<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Tal Perry</title><description>Shouting into the void , now with AI.</description><link>https://talperry.com/</link><language>en</language><copyright>Copyright 2026, Calvin Tran</copyright><lastBuildDate>Tue, 13 Jan 2026 00:00:00 +0100</lastBuildDate><generator>Hugo - gohugo.io</generator><docs>http://cyber.harvard.edu/rss/rss.html</docs><atom:link href="https://talperry.com//atom.xml" rel="self" type="application/atom+xml"/><item><title>Fünf praktische Lektionen für das Serving von Modellen mit Triton Inference Server</title><link>https://talperry.com/de/posts/genai/triton-inference-server/</link><description>&lt;p>Triton Inference Server ist zu einer beliebten Wahl für das produktive Serving von Modellen geworden – und das aus gutem Grund: Er ist schnell, flexibel und leistungsstark. Trotzdem erfordert der effektive Einsatz von Triton ein Verständnis dafür, wo er glänzt – und wo er ganz klar nicht glänzt. Dieser Beitrag sammelt fünf praktische Lektionen aus dem Betrieb von Triton in Production, die ich mir früher gewünscht hätte verinnerlicht zu haben.&lt;/p>
&lt;h2 id="die-richtige-serving-schicht-wählen">Die richtige Serving-Schicht wählen&lt;/h2>
&lt;p>Nicht alle Modelle gehören auf Triton. &lt;strong>Nutze vLLM für generative Modelle; nutze Triton für eher traditionelle Inferenz-Workloads.&lt;/strong>&lt;/p>
&lt;p>LLMs sind gerade überall, und Triton bietet Integrationen sowohl mit TensorRT-LLM als auch mit vLLM. Auf den ersten Blick lässt Triton dadurch wie ein One-Stop-Shop wirken, um alles zu serven – von Bildklassifikatoren bis hin zu Large Language Models.&lt;/p>
&lt;p>In der Praxis habe ich festgestellt, dass Triton im Vergleich zu einem „rohen“ vLLM-Deployment nur sehr wenig zusätzlichen Nutzen bringt. Das ist kein Vorwurf an Triton – es ist ein Spiegelbild dessen, wie unterschiedlich generative Workloads im Vergleich zur klassischen Inferenz sind. Viele von Tritons besten Features lassen sich schlicht nicht sauber auf die Art abbilden, wie LLMs serviert werden.&lt;/p>
&lt;p>Ein paar konkrete Beispiele machen das deutlich:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Dynamisches Batching → Kontinuierliches Batching&lt;/strong>
Tritons dynamischer Batcher wartet kurz, um ganze Requests zu gruppieren, und führt sie dann gemeinsam aus. Das funktioniert extrem gut für Inferenz mit fester Shape. LLM-Serving hingegen profitiert von kontinuierlichem Batching, bei dem neue Requests in einen aktiven Batch eingefügt werden, während andere mit dem Generieren von Tokens fertig werden. Zwar ist das technisch über Tritons vLLM-Backend möglich, aber es ist weder einfach noch naheliegend zu betreiben.&lt;/li>
&lt;/ul>
&lt;picture>
&lt;source srcset="https://talperry.com/en/posts/genai/triton-inference-server/dynamic-vs-continuous-batching_hu5809796310521240948.webp" type="image/webp">
&lt;source srcset="https://talperry.com/en/posts/genai/triton-inference-server/dynamic-vs-continuous-batching_hu6424849289932300575.png" type="image/png">
&lt;img src="https://talperry.com/en/posts/genai/triton-inference-server/dynamic-vs-continuous-batching_hu6424849289932300575.png" alt="Dynamisches Batching vs kontinuierliches Batching" class="article-image" loading="lazy">
&lt;/picture>
&lt;ul>
&lt;li>&lt;strong>Model Packing → Model Sharding&lt;/strong>
Triton macht es leicht, mehrere Modelle auf einer einzelnen GPU zu packen, um die Auslastung zu verbessern. LLMs passen selten in dieses Modell. Selbst eher moderate Modelle belegen oft eine ganze GPU, und größere erfordern Sharding über GPUs oder sogar Nodes hinweg. Triton verhindert das nicht, aber es hilft dabei auch nicht in nennenswertem Maße.&lt;/li>
&lt;/ul>
&lt;picture>
&lt;source srcset="https://talperry.com/en/posts/genai/triton-inference-server/model-sharding-vs-packing_hu16157470664919221625.webp" type="image/webp">
&lt;source srcset="https://talperry.com/en/posts/genai/triton-inference-server/model-sharding-vs-packing_hu11555892844646172857.png" type="image/png">
&lt;img src="https://talperry.com/en/posts/genai/triton-inference-server/model-sharding-vs-packing_hu11555892844646172857.png" alt="Model Sharding vs Model Packing" class="article-image" loading="lazy">
&lt;/picture>
&lt;ul>
&lt;li>&lt;strong>Request-Caching → Prefix-Caching&lt;/strong>
Tritons eingebauter Cache funktioniert, indem er Request–Response-Paare speichert, was für deterministische Workloads sehr effektiv ist. Generative Modelle profitieren stattdessen vom Caching von Zwischenzustand, etwa KV-Caches, die über gemeinsame Prompt-Präfixe indiziert sind. Das ist ein grundsätzlich anderes Problem – und eines, das LLM-native Serving-Systeme deutlich natürlicher lösen.&lt;/li>
&lt;/ul>
&lt;p>Kurz gesagt: Ich habe es durchgehend als deutlich einfacher empfunden, vLLM direkt zu deployen und sofort von kontinuierlichem Batching, Sharding und Prefix-Caching zu profitieren, als Triton darüber zu legen und mich mit Konfiguration herumzuschlagen, um ein ähnliches Verhalten zu erreichen.&lt;/p>
&lt;h2 id="latenz-mit-serverseitigen-timeouts-schützen">Latenz mit serverseitigen Timeouts schützen&lt;/h2>
&lt;p>Dynamisches Batching ist Tritons Killer-Feature. Indem Requests für ein kurzes, konfigurierbares Zeitfenster gepuffert und dann im Batch ausgeführt werden, verbessert Triton die Hardware-Auslastung und eliminiert eine große Menge clientseitiger Komplexität.&lt;/p>
&lt;p>Es gibt jedoch eine wichtige Stolperfalle: Standardmäßig wird Triton keine Requests aus der Warteschlange verwerfen.&lt;/p>
&lt;p>Unter Last ist es durchaus möglich, dass Triton einen Rückstau aufbaut, während Clients timeouten und weiterziehen. Wenn &lt;code>max_queue_delay_microseconds&lt;/code> nicht konfiguriert ist, können diese aufgegebenen Requests in der Queue liegen bleiben und später doch ausgeführt werden, wodurch Ressourcen verbraucht werden, während neuere Requests auf ihre Reihe warten.&lt;/p>
&lt;p>Das Ergebnis ist pervers, aber häufig:&lt;/p>
&lt;ul>
&lt;li>Triton verbringt Zeit damit, Requests zu verarbeiten, die der Client bereits aufgegeben hat.&lt;/li>
&lt;li>Die Latenz steigt, während die Queue veraltete Arbeit abarbeitet.&lt;/li>
&lt;/ul>
&lt;p>Dieses Problem ist besonders ausgeprägt beim Python-Backend. Während einige native Backends Client-Abbrüche erkennen können, überlässt das Python-Backend diese Verantwortung weitgehend dem User-Code. Sobald ein Request deine &lt;code>execute()&lt;/code>-Methode erreicht, läuft er in der Regel bis zum Ende, sofern du nicht explizit auf Cancellation prüfst.&lt;/p>
&lt;p>Wenn dir Latenz wichtig ist – und das ist sie mit an Sicherheit grenzender Wahrscheinlichkeit – sind serverseitige Queue-Timeouts nicht optional.&lt;/p>
&lt;h2 id="client-bibliotheken-minimal-halten">Client-Bibliotheken minimal halten&lt;/h2>
&lt;p>Triton erfordert, dass Clients Modelnamen, Tensor-Namen, Shapes und Datentypen kennen. Das direkt an Application-Entwickler:innen weiterzugeben ist unangenehm, daher lohnt sich ein kleiner Client-Wrapper in der Regel.&lt;/p>
&lt;p>Problematisch wird es, wenn dieser Wrapper Ambitionen entwickelt.&lt;/p>
&lt;p>Ich habe (und gebaut) Client-Bibliotheken gesehen, die hilfreich sein wollen, indem sie Retries, Backoff oder andere Resilience-Features hinzufügen. In der Praxis geht das oft nach hinten los. Requests erneut zu senden, die wegen Overload oder ungültiger Inputs fehlgeschlagen sind, kann den Traffic genau dann verstärken, wenn das System ohnehin schon kämpft – und aus einer vorübergehenden Verlangsamung ein selbstverschuldetes Denial-of-Service machen.&lt;/p>
&lt;p>Das heißt nicht, dass man keine Retries verwenden sollte, sondern dass man sie nicht unsichtbar machen sollte – und dass Callers identifizieren können sollten und identifizierbar sein sollten, wenn Retry-Logik überarbeitet werden muss.&lt;/p>
&lt;p>Meine Empfehlung ist einfach: Halte Client-Bibliotheken langweilig. Lass sie die Request-Konstruktion erledigen und sonst nichts. Implementiere Retries und Error-Handling am Call-Site, wo die Anwendung den nötigen Kontext und die Observability hat, um das Richtige zu tun.&lt;/p>
&lt;h2 id="tritons-eingebauten-cache-nutzen">Tritons eingebauten Cache nutzen&lt;/h2>
&lt;p>Tritons Request–Response-Cache ist leicht zu übersehen, kann aber überraschend effektiv sein – besonders in Cloud-Umgebungen. GPU-Instanzen bringen oft deutlich mehr Systemspeicher mit, als sonst genutzt wird, und ein paar zusätzliche Gigabytes fürs Caching können deiner GPU eine beträchtliche Menge redundanter Arbeit ersparen.&lt;/p>
&lt;p>Das ist keine pauschale Empfehlung – viele Workloads werden nicht profitieren –, aber es lohnt sich zu experimentieren. Cache-Hit-Rates zusammen mit Queue-Depth zu beobachten, kann dir schnell zeigen, ob Caching hilft und ob ein bestimmter Client unnötigen, doppelten Traffic erzeugt.&lt;/p>
&lt;h2 id="threadpoolexecutor-für-clientseitige-parallelität-bevorzugen">ThreadPoolExecutor für clientseitige Parallelität bevorzugen&lt;/h2>
&lt;p>Auf der Client-Seite habe ich festgestellt, dass der einfachste Weg, parallele Inferenz-Requests abzusetzen, auch der beste ist: Verwende einen Thread-Pool.&lt;/p>
&lt;p>In CPython gibt Socket-I/O den GIL frei. Da Tritons HTTP-Client primär I/O-bound ist, ist &lt;code>ThreadPoolExecutor&lt;/code> damit eine effektive und unkomplizierte Wahl:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">infer&lt;/span>(inputs):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> model_client&lt;span style="color:#f92672">.&lt;/span>infer(inputs&lt;span style="color:#f92672">=&lt;/span>inputs)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">with&lt;/span> ThreadPoolExecutor(max_workers&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">8&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> pool:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> results &lt;span style="color:#f92672">=&lt;/span> list(pool&lt;span style="color:#f92672">.&lt;/span>map(infer, batch_of_requests))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Dieser Ansatz hat ein paar schöne Eigenschaften:&lt;/p>
&lt;ol>
&lt;li>Der Client muss keine Batching-Logik implementieren.&lt;/li>
&lt;li>Tritons dynamischer Batcher kann Requests über Threads hinweg und sogar über Clients hinweg aggregieren.&lt;/li>
&lt;li>Nebenläufigkeit ist natürlich begrenzt und liefert damit eine Form von Backpressure.&lt;/li>
&lt;/ol>
&lt;p>Jegliche Python-Arbeit innerhalb von &lt;code>infer&lt;/code> bleibt serialisiert, was sich als Feature statt Bug herausstellt: Es verhindert, dass der Client den Server überrollt, während effizientes paralleles I/O weiterhin möglich bleibt.&lt;/p>
&lt;h2 id="fazit">Fazit&lt;/h2>
&lt;p>Triton ist ein mächtiges Serving-System, aber es ist auch meinungsstark. Es funktioniert am besten, wenn seine Abstraktionen zu dem Workload passen, den du serven willst.&lt;/p>
&lt;p>Für klassische Inferenz-Workloads sind Tritons Batching, Scheduling und Caching schwer zu schlagen. Für LLMs und andere generative Modelle passen speziell dafür gebaute Systeme wie vLLM oft besser. Diese Unterscheidung zu verstehen – und Triton defensiv zu konfigurieren, wenn du es einsetzt – hilft erheblich dabei, zuverlässige Inferenzsysteme mit niedriger Latenz zu bauen.&lt;/p></description><author/><guid>https://talperry.com/de/posts/genai/triton-inference-server/</guid><pubDate>Mon, 15 Dec 2025 10:00:00 +0200</pubDate></item><item><title>Ich bin nicht der Gründer, den diese App verdient</title><link>https://talperry.com/de/posts/scripture-app/</link><description>&lt;p>Bevor ich in die Gründe für meine Entscheidung eintauche, ist es wichtig zu wissen, dass ich ein jüdischer israelischer Atheist bin, der in Berlin lebt. Dieser Hintergrund könnte dich fragen lassen, warum ich überhaupt in Betracht ziehen würde, so eine App zu bauen.&lt;/p>
&lt;p>Trotz meiner grundlegenden Identität habe ich vor zwei Jahren ein &lt;a href="https://lighttag.io">Developer-Tools-Unternehmen&lt;/a> verkauft und geschworen: „Nie wieder baue ich ein Developer-Tools-Unternehmen.“ Stattdessen möchte ich etwas verfolgen, das einen klar definierten Zielmarkt und ein eindeutiges Nutzenversprechen hat und idealerweise kein externes Kapital erfordert.&lt;/p>
&lt;p>Christliche Bibelverse auswendig zu lernen, eine Nische innerhalb des Faithtech-Marktes, wirkte anfangs vielversprechend. Am Ende kam ich jedoch zu dem Schluss, dass es nicht das Richtige für mich war. Hier reflektiere ich, wie ich überhaupt auf die Idee gekommen bin und wie ich zu dem Schluss kam, dass ich nicht dazu passe.&lt;/p>
&lt;h2 id="anfangsmotivation">Anfangsmotivation&lt;/h2>
&lt;p>Als Einwanderer in Deutschland ist das Lernen der Landessprache eine dauerhafte Herausforderung. Ich benutze &lt;a href="https://apps.ankiweb.net/">Anki&lt;/a>, ein Tool, das „Spaced Repetition“ einsetzt, um meinen deutschen Wortschatz zu erweitern.&lt;/p>
&lt;p>Als ich Ankis Wirksamkeit entdeckte, stieß ich auf eine herzerwärmende &lt;a href="https://www.reddit.com/r/Anki/comments/eisra4/update_on_my_daughter_and_anki/">Reddit-Geschichte&lt;/a> über ein Elternteil, das seinem Kind mit diesem Tool das Lesen beibringt. Inspiriert davon brachte ich meinem eigenen fünfjährigen Sohn mit Anki erfolgreich das Lesen bei.
&lt;img
src="./giraffe.jpeg"
alt="GenAI macht eine lustige Art, das Wort Giraffe zu lesen"
loading="lazy"
decoding="async"
class="full-width"
/>
Außerdem stellte ich fest, dass GenAI es mir ermöglicht, große Mengen hochwertiger Inhalte günstig zu erzeugen — etwas, das vor ein paar Jahren unerschwinglich teuer gewesen wäre. Mit GenAI erstellte ich für meinen Sohn ansprechende Lerninhalte, zum Beispiel das Wort „Wurst“ (sausage) mit Bildern von Würsten zu buchstabieren und illustrierte sowie erzählte deutsche Sätze in YouTube-Videos zu produzieren.&lt;/p>
&lt;p>Es hat etwas Schönes, wenn Menschen die Worte verinnerlichen wollen, die sie prägen. Mich reizte die Möglichkeit, das als Produkt an andere Eltern zu verkaufen. Allerdings merkte ich, dass der Markt für Lern-Apps, die Kindern das Lesen beibringen, unattraktiv ist. Der Preis ist niedrig, die Kosten für Kundenakquise sind hoch, die Regulierung ist komplex, und wiederkehrende Abo-Einnahmen sind schwer zu erreichen.&lt;/p>
&lt;p>Trotz dieser Hürden blieb ich am Schnittpunkt von bezahlbaren, hochwertigen Inhalten, die GenAI ermöglicht, und Memorierungs-Algorithmen interessiert. Doch nachdem ich früher den Fehler gemacht hatte, erst etwas zu bauen und dann zu validieren, ob es jemand wollte, suchte ich nun zuerst nach einem Problem, bevor ich eine Lösung entwickelte.&lt;/p>
&lt;p>Eines Tages, getrieben von Neugier, begann ich damit, mehrere Kapitel des Alten Testaments auf Hebräisch mit Anki auswendig zu lernen. Zwar könnte das ein Produkt sein, aber der spezifisch jüdische Fokus begrenzt den potenziellen Markt aufgrund der kleineren jüdischen Weltbevölkerung.&lt;/p>
&lt;p>Im Gegensatz dazu gibt es in den USA viele Christen mit Smartphones und relativ hoher Kaufkraft. Das könnte ein tragfähiger Markt sein, also begann ich, das Auswendiglernen von Bibeltexten für Christen zu erkunden.&lt;/p>
&lt;p>Ich musste auch zugeben: Auch wenn der Markt groß und gut verständlich war, war es nicht meine Geschichte, die ich erzählen sollte, und auch kein Publikum, das ich aus eigener Erfahrung intuitiv verstehen könnte.&lt;/p>
&lt;h2 id="der-deep-dive">Der Deep Dive&lt;/h2>
&lt;p>Ein paar schnelle Google-Suchen zeigten, dass es in den USA etwa 200 Millionen Christen gibt, wobei sich 140 Millionen als Evangelikale identifizieren. Auch wenn ich die Bedeutung davon nicht vollständig erfasste, wusste ich aus sozialen Medien, dass Evangelikale fromm sind und bereit, in ihre Spiritualität zu investieren.&lt;/p>
&lt;p>Die Idee wurde noch attraktiver, als ich den Reichtum an verfügbaren Daten über den potenziellen Markt entdeckte. Im Gegensatz zu meiner Erfahrung mit Developer Tools, wo Marktsegmentierung eine Herausforderung war, fand ich hier detaillierte &lt;a href="https://www.pewresearch.org/religion/2023/06/02/use-of-apps-and-websites-in-religious-life/">Pew-Research-Daten&lt;/a> zur App-Nutzung in verschiedenen Denominationen, zum verfügbaren Einkommen und zur geografischen Verteilung.&lt;/p>
&lt;p>Mit diesen Daten könnte ich gezielt bestimmte Marktsegmente ansprechen und Sprache, Bildwelt sowie Marketingstrategien entsprechend zuschneiden. Ich war überzeugt: Wenn Menschen bereit wären, für diese Lösung zu zahlen, könnte ich effektive Marketing-Experimente entwerfen, um eine Sales-Maschine zu skalieren.&lt;/p>
&lt;h2 id="die-hürde-bei-der-produktentwicklung">Die Hürde bei der Produktentwicklung&lt;/h2>
&lt;p>Skalierbares Marketing klingt vielversprechend, aber eine Marketingkampagne braucht ein funktionierendes Produkt, das man auf den Markt bringen kann. Was bedeutet „funktionierend“ in diesem Kontext? Für Nutzer bedeutet es, dass die App ihnen hilft, Bibeltexte auswendig zu lernen.&lt;/p>
&lt;p>Für mich hingegen — die Person, die Zeit und Geld in den Aufbau investiert — bedeutet eine funktionierende App eine App, die Nutzer in zahlende Kunden umwandelt und sie hält.&lt;/p>
&lt;p>Ein Produkt als umsatzgenerierende Maschine zu betrachten, verkompliziert den Umfang eines MVP. Dazu gehören passende Microcopy, korrekte Preisgestaltung, ein schneller „Wow!“-Moment und die Sicherstellung von Nutzerbindung.&lt;/p>
&lt;p>Das klingt zwar machbar, aber auch schwierig, teuer und zeitaufwendig. Ich stellte mir ein paar Fragen: Könnte ich das ohne Venture-Capital-Finanzierung schaffen? Wahrscheinlich nicht. Habe ich Expertise darin, Consumer-Apps zu bauen, die konvertieren? Nein. Habe ich Einblicke darin, wie man die App viral macht? Nein.&lt;/p>
&lt;p>Meine Begeisterung ließ nach, und eine Erkenntnis in einem Gespräch mit meiner Frau besiegelte die Entscheidung.&lt;/p>
&lt;h2 id="die-marketing-herausforderung">Die Marketing-Herausforderung&lt;/h2>
&lt;p>Während ich mit meiner Frau im Stau über die Idee sprach, sang Maria zu &lt;a href="https://genius.com/Carrie-underwood-before-he-cheats-lyrics">Carrie Underwoods „Before He Cheats“&lt;/a> mit, wo sie mit folgender Zeile ein ganzes Universum einfängt:&lt;/p>
&lt;blockquote>
&lt;p>„Right now, he&amp;rsquo;s probably buying her some fruity little drink &amp;lsquo;Cause she can&amp;rsquo;t shoot a whiskey,”&lt;/p>
&lt;/blockquote>
&lt;picture>
&lt;source srcset="https://talperry.com/en/posts/scripture-app/before-he-cheats_hu17757550046027709510.webp" type="image/webp">
&lt;source srcset="https://talperry.com/en/posts/scripture-app/before-he-cheats_hu17757550046027709510.webp" type="image/webp">
&lt;img src="https://talperry.com/en/posts/scripture-app/before-he-cheats_hu17757550046027709510.webp" alt="Carrie Underwood — Before He Cheats" class="article-image" loading="lazy">
&lt;/picture>
&lt;p>Das machte die tiefe Kenntnis der Songwriter über ihr Publikum deutlich. „Shooting whiskey“ ist für ihr Publikum eine eindrückliche Formulierung, für mich aber relativ bedeutungslos (ein Israeli in Berlin, wo Whiskey kein kulturelles Grundelement ist). Die Songwriter kannten ihr Publikum so gut, dass sie solche evocative Phrasen intuitiv finden konnten.&lt;/p>
&lt;p>Wenn ich Software zum Auswendiglernen von Bibeltexten an amerikanische Christen verkaufen wollte, was könnte ich dann über sie intuitiv wissen? Welche Relevanz oder welchen Vorteil habe ich dabei, ein Produkt zu schaffen, das eine Identität berührt, die ich nicht teile?&lt;/p>
&lt;p>Mir wurde klar, dass mir nicht nur die Marketingsprache fehlte — mir fehlte der gelebte Kontext, der überhaupt prägt, warum das Auswendiglernen von Bibeltexten wichtig ist.&lt;/p>
&lt;p>Dieses Problem lässt sich mit Geld lösen. Ich könnte eine Agentur beauftragen, die auf das christliche Segment spezialisiert ist. Aber ohne ein marktreifes Produkt: Warum in Marketing investieren? Und ohne eine klare Marketingstrategie: Warum das Produkt bauen?&lt;/p>
&lt;h2 id="persönliche-passung-und-marktverständnis">Persönliche Passung und Marktverständnis&lt;/h2>
&lt;p>Sowohl Produkt- als auch Marketing-Herausforderungen lassen sich mit Zeit und Geld lösen. Aber ich musste mich fragen: Wie viel Zeit? Wie viel meines Lebens bin ich bereit, dem Bauen und Verkaufen von Software zum Auswendiglernen von Bibeltexten zu widmen?&lt;/p>
&lt;p>Ja, ich würde Menschen gern dabei helfen, ihre Spiritualität zu vertiefen. Ja, es wäre intellektuell stimulierend. Ja, es könnte lukrativ sein. Aber ich habe keine persönliche Verbindung zum Produkt oder zur Community. Ist das wirklich, wie ich die nächsten 5–10 Jahre meines Lebens verbringen will?&lt;/p>
&lt;p>Nein, ist es nicht.&lt;/p>
&lt;p>Die Frage war nicht, ob ich es bauen könnte — sondern warum ich es würde. Es gibt viele gute Probleme, aber nicht alle davon sind meine.&lt;/p>
&lt;h2 id="fazit">Fazit&lt;/h2>
&lt;p>Ich war anfangs begeistert von dieser Chance, weil sie vertraute Technologie nutzte, einen großen und klar definierten Markt hatte und potenziell lukrativ wirkte. Doch ich erkannte, dass ohne einen persönlichen Vorteil in diesem Bereich die Kosten (an Zeit und Geld), selbst nur ein MVP zu entwickeln, mehr waren, als ich bereit war zu investieren.&lt;/p>
&lt;p>Aus der Erkundung eines Marktes wurde eine Erkundung von Identität.&lt;/p></description><author/><guid>https://talperry.com/de/posts/scripture-app/</guid><pubDate>Tue, 14 May 2024 10:07:22 +0200</pubDate></item><item><title>Convolutional Methods for Text</title><link>https://talperry.com/de/posts/classics/cmft/</link><description>&lt;h3 id="tldr">tl;dr&lt;/h3>
&lt;ul>
&lt;li>RNNs funktionieren großartig für Text, aber Convolutions können es schneller&lt;/li>
&lt;li>Jeder Teil eines Satzes kann die Semantik eines Wortes beeinflussen. Deshalb wollen wir, dass unser Netzwerk die gesamte Eingabe auf einmal sieht&lt;/li>
&lt;li>Ein so großes rezeptives Feld zu bekommen, kann Gradienten verschwinden lassen und unsere Netzwerke scheitern lassen&lt;/li>
&lt;li>Das Vanishing-Gradient-Problem können wir mit DenseNets oder dilatierten Convolutions lösen&lt;/li>
&lt;li>Manchmal müssen wir Text generieren. Wir können „Deconvolutions“ verwenden, um Ausgaben beliebiger Länge zu erzeugen.&lt;/li>
&lt;/ul>
&lt;h3 id="intro">Intro&lt;/h3>
&lt;p>In den letzten drei Jahren hat das Feld des NLP dank Deep Learning eine enorme Revolution erlebt. Der Anführer dieser Revolution war das rekurrente neuronale Netzwerk und insbesondere seine Ausprägung als LSTM. Parallel dazu wurde das Feld des Computer Vision durch Convolutional Neural Networks umgeformt. Dieser Post untersucht, was wir „Text-Leute“ von unseren Freunden lernen können, die Vision machen.&lt;/p>
&lt;h3 id="häufige-nlp-aufgaben">Häufige NLP-Aufgaben&lt;/h3>
&lt;p>Um die Bühne zu bereiten und uns auf ein Vokabular zu einigen, möchte ich ein paar der gängigeren Aufgaben in NLP vorstellen. Der Konsistenz halber nehme ich an, dass alle Eingaben unseres Modells Zeichen sind und dass unsere „Beobachtungseinheit“ ein Satz ist. Beide Annahmen sind nur der Bequemlichkeit halber, und du kannst Zeichen durch Wörter und Sätze durch Dokumente ersetzen, wenn du möchtest.&lt;/p>
&lt;h4 id="klassifikation">Klassifikation&lt;/h4>
&lt;p>Vielleicht der älteste Trick im Buch: Oft wollen wir einen Satz klassifizieren. Zum Beispiel möchten wir eine E-Mail-Betreffzeile als Hinweis auf Spam klassifizieren, die Stimmung einer Produktrezension erraten oder einem Dokument ein Thema zuweisen.&lt;/p>
&lt;p>Der naheliegendste Weg, diese Art von Aufgabe mit einem RNN zu handhaben, ist, den gesamten Satz Zeichen für Zeichen einzuspeisen und dann den finalen Hidden State des RNN zu beobachten.&lt;/p>
&lt;h4 id="sequenz-labeling">Sequenz-Labeling&lt;/h4>
&lt;p>Sequenz-Labeling-Aufgaben sind Aufgaben, die für jede Eingabe eine Ausgabe zurückgeben. Beispiele sind Part-of-Speech-Tagging oder Entity-Recognition-Aufgaben. Während das Bare-Bones-LSTM-Modell weit vom State of the Art entfernt ist, ist es leicht zu implementieren und liefert überzeugende Ergebnisse. Siehe &lt;a href="https://arxiv.org/pdf/1508.01991.pdf">dieses Paper&lt;/a> für eine ausgefeiltere Architektur&lt;/p>
&lt;p>&lt;img
src="./bilstm-ner-sequence-labeling.webp"
alt="Bidirektionale LSTM-Architektur für Sequenz-Labeling"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h4 id="sequenzgenerierung">Sequenzgenerierung&lt;/h4>
&lt;p>Vermutlich die beeindruckendsten Ergebnisse im modernen NLP gab es bei der Übersetzung. Übersetzung ist eine Abbildung von einer Sequenz auf eine andere, ohne Garantien für die Länge des Ausgabesatzes. Zum Beispiel ist die Übersetzung der ersten Worte der Bibel von Hebräisch nach Englisch: בראשית = „In the Beginning“.&lt;/p>
&lt;p>Im Zentrum dieses Erfolgs steht das Sequence-to-Sequence- (aka Encoder-Decoder-)Framework, eine Methodik, eine Sequenz in einen Code zu „komprimieren“ und sie dann in eine andere Sequenz zu dekodieren. Bemerkenswerte Beispiele sind Übersetzung (Hebräisch encodieren und nach Englisch decodieren), Bildbeschreibung (ein Bild encodieren und eine textuelle Beschreibung seines Inhalts decodieren)&lt;/p>
&lt;p>&lt;img
src="./cnn-attention-image-captioning.webp"
alt="Pipeline für Bildbeschreibung mit CNN-Features, die einen Attention-LSTM-Decoder speisen"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Der grundlegende Encoder-Schritt ist ähnlich zu dem Schema, das wir für Klassifikation beschrieben haben. Das Erstaunliche ist, dass wir einen Decoder bauen können, der lernt, Ausgaben beliebiger Länge zu generieren.&lt;/p>
&lt;p>Die beiden Beispiele oben sind eigentlich beide Übersetzung, aber Sequenzgenerierung ist etwas breiter als das. OpenAI hat kürzlich &lt;a href="https://blog.openai.com/unsupervised-sentiment-neuron/">ein Paper veröffentlicht&lt;/a>, in dem sie lernen, „Amazon Reviews“ zu generieren, während sie die Sentiment-Ausrichtung der Ausgabe steuern&lt;/p>
&lt;p>&lt;img
src="./sentiment-controlled-examples.webp"
alt="Generierte Amazon-Reviews mit Sentiment auf positiv oder negativ beschränkt"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Ein weiterer persönlicher Favorit ist das Paper &lt;a href="https://arxiv.org/pdf/1511.06349.pdf">Generating Sentences from a Continuous Space&lt;/a>. In diesem Paper trainierten sie einen Variational Autoencoder auf Text, was zur Fähigkeit führte, zwischen zwei Sätzen zu interpolieren und kohärente Ergebnisse zu erhalten.&lt;/p>
&lt;p>&lt;img
src="./sentence-interpolation-samples.webp"
alt="Beispiele für Satzinterpolation aus einem Variational Autoencoder"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h3 id="anforderungen-an-eine-nlp-architektur">Anforderungen an eine NLP-Architektur&lt;/h3>
&lt;p>Was alle Implementierungen, die wir uns angesehen haben, gemeinsam haben, ist, dass sie eine rekurrente Architektur verwenden, normalerweise ein LSTM (Falls du nicht sicher bist, was das ist: &lt;a href="http://karpathy.github.io/2015/05/21/rnn-effectiveness/">hier&lt;/a> ist eine großartige Einführung). Bemerkenswert ist, dass keine der Aufgaben „rekurrent“ im Namen hatte und keine LSTMs erwähnte. Vor diesem Hintergrund lass uns einen Moment darüber nachdenken, was RNNs und insbesondere LSTMs liefern, das sie so allgegenwärtig im NLP macht.&lt;/p>
&lt;h4 id="beliebige-eingabegröße">Beliebige Eingabegröße&lt;/h4>
&lt;p>Ein Standard-Feedforward-Neuronales-Netzwerk hat einen Parameter für jede Eingabe. Das wird problematisch, wenn man mit Text oder Bildern arbeitet, aus ein paar Gründen.&lt;/p>
&lt;ol>
&lt;li>Es schränkt die Eingabegröße ein, die wir handhaben können. Unser Netzwerk wird eine endliche Anzahl an Eingabeknoten haben und nicht darüber hinaus wachsen können.&lt;/li>
&lt;li>Wir verlieren eine Menge gemeinsamer Information. Betrachte die Sätze „I like to drink beer a lot“ und „I like to drink a lot of beer“. Ein Feedforward-Netzwerk müsste das Konzept von „a lot“ zweimal lernen, da es jedes Mal in unterschiedlichen Eingabeknoten erscheint.&lt;/li>
&lt;/ol>
&lt;p>Rekurrente neuronale Netzwerke lösen dieses Problem. Statt einen Knoten pro Eingabe zu haben, haben wir eine große „Box“ von Knoten, die wir immer wieder auf die Eingabe anwenden. Die „Box“ lernt eine Art Übergangsfunktion, was bedeutet, dass die Ausgaben einer Rekurrenzrelation folgen, daher der Name.&lt;/p>
&lt;p>Erinnere dich daran, dass die &lt;em>Vision-Leute&lt;/em> einen sehr ähnlichen Effekt für Bilder mit Convolutions bekommen haben. Das heißt: Statt einen Eingabeknoten pro Pixel zu haben, erlaubten Convolutions die Wiederverwendung desselben kleinen Parametersatzes über das gesamte Bild.&lt;/p>
&lt;h4 id="langzeitabhängigkeiten">Langzeitabhängigkeiten&lt;/h4>
&lt;p>Das Versprechen von RNNs ist ihre Fähigkeit, Langzeitabhängigkeiten implizit zu modellieren. Das Bild unten stammt von OpenAI. Sie trainierten ein Modell, das am Ende Sentiment erkannte, und färbten den Text Zeichen für Zeichen mit der Ausgabe des Modells ein. Beachte, wie das Modell das Wort „best“ sieht und ein positives Sentiment auslöst, das es über mehr als 100 Zeichen mit sich trägt. Das ist das Erfassen einer weitreichenden Abhängigkeit.&lt;/p>
&lt;p>&lt;img
src="./sentiment-heatmap.webp"
alt="Heatmap der Aktivierung eines Sentiment-Neurons über Review-Text"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Die Theorie der RNNs verspricht uns Langzeitabhängigkeiten out of the box. In der Praxis ist es etwas schwieriger. Wenn wir via Backpropagation lernen, müssen wir das Signal durch die gesamte Rekurrenzrelation propagieren. Das Ding ist: Bei jedem Schritt multiplizieren wir am Ende mit einer Zahl. Wenn diese Zahlen im Allgemeinen kleiner als 1 sind, geht unser Signal schnell gegen 0. Wenn sie größer als 1 sind, explodiert unser Signal.&lt;/p>
&lt;p>Diese Probleme nennt man Vanishing und Exploding Gradient und sie werden im Allgemeinen durch LSTMs und ein paar clevere Tricks gelöst. Ich erwähne sie jetzt, weil wir diesen Problemen mit Convolutions wieder begegnen werden und einen anderen Weg brauchen, sie zu adressieren.&lt;/p>
&lt;h3 id="vorteile-von-convolutions">Vorteile von Convolutions&lt;/h3>
&lt;p>Bisher haben wir gesehen, wie großartig LSTMs sind, aber dieser Post handelt von Convolutions. Im Sinne von &lt;em>don’t fix what ain’t broken&lt;/em> müssen wir uns fragen, warum wir überhaupt Convolutions verwenden wollen würden.&lt;/p>
&lt;p>Eine Antwort ist: „Weil wir es können“.&lt;/p>
&lt;p>Aber es gibt zwei andere überzeugende Gründe, Convolutions zu verwenden: Geschwindigkeit und Kontext.&lt;/p>
&lt;h4 id="parallelisierung">Parallelisierung&lt;/h4>
&lt;p>RNNs arbeiten sequenziell: Die Ausgabe für die zweite Eingabe hängt von der ersten ab, und so können wir ein RNN nicht parallelisieren. Convolutions haben dieses Problem nicht; jeder „Patch“, auf dem ein Convolutional Kernel arbeitet, ist unabhängig von den anderen, was bedeutet, dass wir über die gesamte Eingabeschicht gleichzeitig gehen können.&lt;/p>
&lt;p>Dafür gibt es einen Preis: Wie wir sehen werden, müssen wir Convolutions zu tiefen Schichten stapeln, um die gesamte Eingabe zu sehen, und jede dieser Schichten wird sequenziell berechnet. Aber die Berechnungen in jeder Schicht passieren gleichzeitig und jede einzelne Berechnung ist klein (im Vergleich zu einem LSTM), sodass wir in der Praxis eine große Beschleunigung bekommen.&lt;/p>
&lt;p>Als ich anfing, das zu schreiben, hatte ich nur meine eigene Erfahrung und Googles ByteNet, um diese Behauptung zu stützen. Gerade diese Woche hat Facebook ihr vollständig convolutionales Übersetzungsmodell veröffentlicht und eine 9-fache Beschleunigung gegenüber LSTM-basierten Modellen berichtet.&lt;/p>
&lt;h4 id="die-gesamte-eingabe-auf-einmal-sehen">Die gesamte Eingabe auf einmal sehen&lt;/h4>
&lt;p>LSTMs lesen ihre Eingabe von links nach rechts (oder von rechts nach links), aber manchmal möchten wir, dass der Kontext vom Ende des Satzes die Gedanken des Netzwerks über den Anfang beeinflusst. Zum Beispiel könnten wir einen Satz haben wie „I’d love to buy your product. Not!“ und wir möchten, dass diese Negation am Ende den gesamten Satz beeinflusst.&lt;/p>
&lt;p>Mit LSTMs erreichen wir das, indem wir zwei LSTMs laufen lassen: eines von links nach rechts und das andere von rechts nach links, und ihre Ausgaben konkatenieren. Das funktioniert in der Praxis gut, verdoppelt aber unsere Rechenlast.&lt;/p>
&lt;p>Convolutions dagegen bekommen ein größeres „rezeptives Feld“, wenn wir mehr und mehr Schichten stapeln. Das bedeutet, dass standardmäßig jeder „Schritt“ in der Darstellung der Convolution die gesamte Eingabe in seinem rezeptiven Feld sieht, von davor und danach. Mir ist kein definitives Argument bekannt, dass dies inhärent besser ist als ein LSTM, aber es gibt uns den gewünschten Effekt auf kontrollierbare Weise und mit geringer Rechenkosten.&lt;/p>
&lt;p>Bisher haben wir unser Problemfeld abgesteckt und ein wenig über die konzeptuellen Vorteile von Convolutions für NLP gesprochen. Ab hier möchte ich diese Konzepte in praktische Methoden übersetzen, die wir verwenden können, um unsere Netzwerke zu analysieren und zu konstruieren.&lt;/p>
&lt;h3 id="praktische-convolutions-für-text">Praktische Convolutions für Text&lt;/h3>
&lt;p>&lt;img
src="./convolution-animation.webp"
alt="Animierte Visualisierung eines Convolutional Kernels, der über ein Bild gleitet"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Du hast wahrscheinlich eine Animation wie die oben gesehen, die illustriert, was eine Convolution macht. Unten ist ein Eingabebild, oben ist das Ergebnis, und der graue Schatten ist der Convolutional Kernel, der wiederholt angewendet wird.&lt;/p>
&lt;p>Das ergibt alles perfekt Sinn, außer dass die in dem Bild beschriebene Eingabe ein Bild ist, mit zwei räumlichen Dimensionen (Höhe und Breite). Wir sprechen über Text, der nur eine Dimension hat, und die ist temporal, nicht räumlich.&lt;/p>
&lt;p>Für alle praktischen Zwecke macht das keinen Unterschied. Wir müssen nur an unseren Text als ein Bild mit Breite &lt;em>n&lt;/em> und Höhe 1 denken. Tensorflow bietet dafür eine conv1d-Funktion, aber sie stellt andere convolutionale Operationen nicht in ihrer 1d-Version bereit.&lt;/p>
&lt;p>Um die Idee „Text = ein Bild der Höhe 1“ konkret zu machen, schauen wir uns an, wie wir den 2d-Convolution-Op in Tensorflow auf eine Sequenz eingebetteter Tokens anwenden würden.&lt;/p>
&lt;p>Was wir hier tun, ist die Form der Eingabe mit tf.expand_dims zu ändern, sodass sie zu einem „Bild der Höhe 1“ wird. Nachdem wir den 2d-Convolution-Operator ausgeführt haben, drücken wir die zusätzliche Dimension wieder weg.&lt;/p>
&lt;h3 id="hierarchie-und-rezeptive-felder">Hierarchie und rezeptive Felder&lt;/h3>
&lt;p>&lt;img
src="./cnn-receptive-hierarchy.webp"
alt="Hierarchie von CNN-Filtern, die von Kanten zu Gesichtern fortschreitet"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Viele von uns haben Bilder wie das oben gesehen. Es zeigt grob die Hierarchie von Abstraktionen, die ein CNN auf Bildern lernt. In der ersten Schicht lernt das Netzwerk grundlegende Kanten. In der nächsten Schicht kombiniert es diese Kanten, um abstraktere Konzepte wie Augen und Nasen zu lernen. Schließlich kombiniert es diese, um einzelne Gesichter zu erkennen.&lt;/p>
&lt;p>Mit Blick darauf müssen wir uns daran erinnern, dass jede Schicht nicht nur abstraktere Kombinationen der vorherigen Schicht lernt. Aufeinanderfolgende Schichten sehen, implizit oder explizit, mehr von der Eingabe.&lt;/p>
&lt;p>&lt;img
src="./hierarchical-receptive-field-tree.webp"
alt="Baum des rezeptiven Feldes, der schichtweise Aggregation über Eingaben zeigt"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h4 id="rezeptives-feld-vergrößern">Rezeptives Feld vergrößern&lt;/h4>
&lt;p>Bei Vision wollen wir oft, dass das Netzwerk ein oder mehrere Objekte im Bild identifiziert und andere ignoriert. Das heißt, wir interessieren uns für ein lokales Phänomen, aber nicht für eine Beziehung, die sich über die gesamte Eingabe erstreckt.&lt;/p>
&lt;p>&lt;img
src="./hotdog-classifier-example.webp"
alt="Hotdog-Klassifikations-App, die Hotdog- und Schuh-Fotos vergleicht"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Text ist subtiler, da wir oft wollen, dass Zwischenrepräsentationen unserer Daten so viel Kontext über ihre Umgebung wie möglich tragen. Mit anderen Worten: Wir wollen ein möglichst großes rezeptives Feld haben. Es gibt ein paar Wege, das zu erreichen.&lt;/p>
&lt;h4 id="größere-filter">Größere Filter&lt;/h4>
&lt;p>Der erste, offensichtlichste Weg ist, die Filtergröße zu erhöhen, also eine [1x5]-Convolution statt einer [1x3]. In meiner Arbeit mit Text habe ich damit keine großartigen Ergebnisse erzielt, und ich werde meine Spekulationen dazu anbieten, warum.&lt;/p>
&lt;p>In meinem Bereich arbeite ich meist mit Eingaben auf Zeichenebene und mit Texten, die morphologisch sehr reich sind. Ich denke an (zumindest die ersten) Convolution-Schichten als das Lernen von n-Grammen, sodass die Breite des Filters Bigrams, Trigrams usw. entspricht. Wenn das Netzwerk früh größere n-Gramme lernt, sieht es weniger Beispiele, da es in einem Text mehr Vorkommen von „ab“ als von „abb“ gibt.&lt;/p>
&lt;p>Ich habe diese Interpretation nie bewiesen, aber ich habe konsistent schlechtere Ergebnisse mit Filterbreiten größer als 3 bekommen.&lt;/p>
&lt;h4 id="schichten-hinzufügen">Schichten hinzufügen&lt;/h4>
&lt;p>Wie wir im Bild oben gesehen haben, erhöht das Hinzufügen weiterer Schichten das rezeptive Feld. &lt;a href="https://medium.com/u/b04dc6044cc">Dang Ha The Hien&lt;/a> schrieb einen &lt;a href="https://medium.com/@nikasa1889/a-guide-to-receptive-field-arithmetic-for-convolutional-neural-networks-e0f514068807">großartigen Guide&lt;/a> zur Berechnung des rezeptiven Feldes in jeder Schicht, den ich dir empfehle zu lesen.&lt;/p>
&lt;p>Schichten hinzuzufügen hat zwei unterschiedliche, aber zusammenhängende Effekte. Der eine, der oft herumgeworfen wird, ist, dass das Modell lernt, höherstufige Abstraktionen über die Eingaben zu bilden, die es bekommt (Pixels =&amp;gt;Edges =&amp;gt; Eyes =&amp;gt; Face). Der andere ist, dass das rezeptive Feld bei jedem Schritt wächst.&lt;/p>
&lt;p>Das bedeutet, dass unser Netzwerk bei genügend Tiefe die gesamte Eingabeschicht betrachten könnte, wenn auch vielleicht durch einen Schleier von Abstraktionen. Leider kann hier das Vanishing-Gradient-Problem sein hässliches Haupt erheben.&lt;/p>
&lt;h4 id="der-gradient-rezeptives-feld-trade-off">Der Gradient-/Rezeptives-Feld-Trade-off&lt;/h4>
&lt;p>Neuronale Netze sind Netze, durch die Information fließt. Im Forward Pass fließt unsere Eingabe und transformiert sich, hoffentlich zu einer Repräsentation werdend, die besser zu unserer Aufgabe passt. Während der Back Phase propagieren wir ein Signal, den Gradienten, zurück durch das Netzwerk. Genau wie in Vanilla-RNNs wird dieses Signal häufig multipliziert, und wenn es durch eine Reihe von Zahlen geht, die kleiner als 1 sind, dann wird es gegen 0 verblassen. Das bedeutet, dass unser Netzwerk am Ende sehr wenig Signal hat, aus dem es lernen kann.&lt;/p>
&lt;p>Damit bleiben wir mit einem gewissen Trade-off zurück. Einerseits möchten wir so viel Kontext wie möglich aufnehmen können. Andererseits riskieren wir, wenn wir versuchen, unsere rezeptiven Felder durch das Stapeln von Schichten zu vergrößern, verschwindende Gradienten und ein Scheitern daran, überhaupt etwas zu lernen.&lt;/p>
&lt;h3 id="zwei-lösungen-für-das-vanishing-gradient-problem">Zwei Lösungen für das Vanishing-Gradient-Problem&lt;/h3>
&lt;p>Glücklicherweise haben viele kluge Menschen über diese Probleme nachgedacht. Noch glücklicher: Das sind keine Probleme, die einzigartig für Text sind; auch die &lt;em>Vision-Leute&lt;/em> wollen größere rezeptive Felder und gradientenreiche Signale. Schauen wir uns ein paar ihrer verrückten Ideen an und nutzen sie, um unseren eigenen textuellen Ruhm zu mehren.&lt;/p>
&lt;h4 id="residual-connections">Residual Connections&lt;/h4>
&lt;p>2016 war ein weiteres großartiges Jahr für die &lt;em>Vision-Leute&lt;/em>, mit mindestens zwei sehr populären Architekturen, die entstanden sind: &lt;a href="https://arxiv.org/abs/1512.03385">ResNets&lt;/a> und &lt;a href="https://arxiv.org/abs/1608.06993">DenseNets&lt;/a> (Das DenseNet-Paper ist insbesondere außergewöhnlich gut geschrieben und sehr lesenswert). Beide adressieren dasselbe Problem: „Wie mache ich mein Netzwerk sehr tief, ohne das Gradientensignal zu verlieren?“&lt;/p>
&lt;p>&lt;a href="https://medium.com/u/18dfe63fa7f0">Arthur Juliani&lt;/a> schrieb eine fantastische Übersicht über &lt;a href="https://chatbotslife.com/resnets-highwaynets-and-densenets-oh-my-9bb15918ee32">Resnet, DenseNets und Highway Networks&lt;/a> für diejenigen unter euch, die Details und Vergleiche suchen. Ich werde kurz DenseNets anreißen, die das Kernkonzept ins Extrem treiben.&lt;/p>
&lt;p>&lt;img
src="./densenet-connections.webp"
alt="DenseNet-Block mit dicht verbundenen Convolution-Schichten"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Die allgemeine Idee ist, die Distanz zwischen dem Signal, das vom Loss des Netzwerks kommt, und jeder einzelnen Schicht zu reduzieren. Das wird erreicht, indem eine Residual-/Direktverbindung zwischen jeder Schicht und ihren Vorgängern hinzugefügt wird. Dadurch kann der Gradient von jeder Schicht direkt zu ihren Vorgängern fließen.&lt;/p>
&lt;p>DenseNets machen das auf eine besonders interessante Weise. Sie konkatenieren die Ausgabe jeder Schicht mit ihrer Eingabe, sodass:&lt;/p>
&lt;ol>
&lt;li>Wir beginnen mit einem Embedding unserer Eingaben, sagen wir der Dimension 10.&lt;/li>
&lt;li>Unsere erste Schicht berechnet 10 Feature Maps. Sie gibt die 10 Feature Maps aus, konkateniert mit dem ursprünglichen Embedding.&lt;/li>
&lt;li>Die zweite Schicht bekommt als Eingabe 20-dimensionale Vektoren (10 aus der Eingabe und 10 aus der vorherigen Schicht) und berechnet weitere 10 Feature Maps. Somit gibt sie 30-dimensionale Vektoren aus.&lt;/li>
&lt;/ol>
&lt;p>Und so weiter und so weiter, für so viele Schichten, wie du möchtest. Das Paper beschreibt eine Menge Tricks, um Dinge handhabbar und effizient zu machen, aber das ist die grundlegende Prämisse, und das Vanishing-Gradient-Problem ist gelöst.&lt;/p>
&lt;p>Es gibt zwei weitere Dinge, die ich hervorheben möchte.&lt;/p>
&lt;ol>
&lt;li>Ich erwähnte zuvor, dass obere Schichten eine Sicht auf die ursprüngliche Eingabe haben können, die durch Schichten von Abstraktionen vernebelt ist. Einer der Highlights des Konkatenierens der Ausgaben jeder Schicht ist, dass das ursprüngliche Signal die folgenden Schichten intakt erreicht, sodass alle Schichten eine direkte Sicht auf niedrigstufige Features haben, was im Wesentlichen einen Teil dieses Nebels entfernt.&lt;/li>
&lt;li>Der Residual-Connection-Trick erfordert, dass alle unsere Schichten dieselbe Form haben. Das bedeutet, dass wir jede Schicht so padden müssen, dass ihre Eingabe- und Ausgabedimensionen im Raum gleich sind [1Xwidth]. Das bedeutet, dass diese Art Architektur für sich genommen für Sequence-Labeling-Aufgaben funktioniert (bei denen Eingabe und Ausgabe dieselben räumlichen Dimensionen haben), aber mehr Arbeit für Encoding- und Klassifikationsaufgaben benötigt (bei denen wir die Eingabe auf einen Vektor fester Größe oder ein Set von Vektoren reduzieren müssen). Das DenseNet-Paper behandelt das tatsächlich, da ihr Ziel Klassifikation ist, und wir werden diesen Punkt später weiter ausführen.&lt;/li>
&lt;/ol>
&lt;h4 id="dilatierte-convolutions">Dilatierte Convolutions&lt;/h4>
&lt;p>Dilatierte Convolutions aka &lt;em>atrous&lt;/em> Convolutions aka Convolutions mit Löchern sind eine weitere Methode, das rezeptive Feld zu vergrößern, ohne die Gradientengötter zu verärgern. Als wir uns bisher das Stapeln von Schichten angesehen haben, sahen wir, dass das rezeptive Feld linear mit der Tiefe wächst. Dilatierte Convolutions lassen uns das rezeptive Feld exponentiell mit der Tiefe wachsen.&lt;/p>
&lt;p>Du findest eine fast zugängliche Erklärung von dilatierten Convolutions im Paper &lt;a href="https://arxiv.org/pdf/1511.07122.pdf">Multi scale context aggregation by dilated convolutions&lt;/a>, das sie für Vision nutzt. Obwohl konzeptionell einfach, hat es eine Weile gedauert, bis ich genau verstand, was sie tun, und ich könnte es immer noch nicht ganz richtig verstanden haben.&lt;/p>
&lt;p>Die Grundidee ist, „Löcher“ in jeden Filter einzuführen, sodass er nicht auf benachbarten Teilen der Eingabe arbeitet, sondern sie überspringt zu weiter entfernten Teilen. Beachte, dass das anders ist als eine Convolution mit Stride &amp;gt; 1 anzuwenden. Wenn wir einen Filter striden, überspringen wir Teile der Eingabe zwischen Anwendungen der Convolution. Bei dilatierten Convolutions überspringen wir Teile der Eingabe innerhalb einer einzelnen Anwendung der Convolution. Indem wir clever wachsende Dilatationen anordnen, können wir das versprochene exponentielle Wachstum der rezeptiven Felder erreichen.&lt;/p>
&lt;p>Wir haben bisher viel Theorie besprochen, aber wir sind endlich an einem Punkt, an dem wir dieses Zeug in Aktion sehen können!&lt;/p>
&lt;p>Ein persönliches Lieblingspaper ist &lt;a href="https://arxiv.org/pdf/1610.10099.pdf">Neural Machine Translation in Linear Time&lt;/a>. Es folgt der Encoder-Decoder-Struktur, über die wir am Anfang gesprochen haben. Wir haben noch nicht alle Werkzeuge, um über den Decoder zu sprechen, aber wir können den Encoder in Aktion sehen.&lt;/p>
&lt;p>&lt;img
src="./dilated-convolution-receptive-field.webp"
alt="Encoder mit dilatierten Convolutions und expandierenden rezeptiven Feldern über eine Sequenz"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Und hier ist eine englische Eingabe&lt;/p>
&lt;blockquote>
&lt;p>Director Jon Favreau, who is currently working on Disney’s forthcoming Jungle Book film, told the website Hollywood Reporter: “I think times are changing.”&lt;/p>
&lt;/blockquote>
&lt;p>Und ihre Übersetzung, präsentiert von dilatierten Convolutions&lt;/p>
&lt;blockquote>
&lt;p>Regisseur Jon Favreau, der zur Zeit an Disneys kommendem Jungle Book Film arbeitet, hat der Website Hollywood Reporter gesagt: “Ich denke, die Zeiten andern sich”.&lt;/p>
&lt;/blockquote>
&lt;p>Und als Bonus: Denk daran, dass Sound genau wie Text ist, in dem Sinne, dass er nur eine räumliche/temporale Dimension hat. Schau dir DeepMinds &lt;a href="https://deepmind.com/blog/wavenet-generative-model-raw-audio/">Wavenet&lt;/a> an, das dilatierte Convolutions (und eine Menge anderer Magie) nutzt, um &lt;a href="https://storage.googleapis.com/deepmind-media/pixie/knowing-what-to-say/second-list/speaker-1.wav">menschlich klingende Sprache&lt;/a> und &lt;a href="https://storage.googleapis.com/deepmind-media/pixie/making-music/sample_4.wav">Klaviermusik&lt;/a> zu generieren.&lt;/p>
&lt;h3 id="etwas-aus-deinem-netzwerk-herausbekommen">Etwas aus deinem Netzwerk herausbekommen&lt;/h3>
&lt;p>Als wir DenseNets besprochen haben, erwähnte ich, dass der Einsatz von Residual Connections uns dazu zwingt, Eingabe- und Ausgabelänge unserer Sequenz gleich zu halten, was durch Padding erreicht wird. Das ist großartig für Aufgaben, bei denen wir jedes Element in unserer Sequenz labeln müssen, zum Beispiel:&lt;/p>
&lt;ul>
&lt;li>Beim Part-of-Speech-Tagging, wo jedes Wort eine Wortart ist.&lt;/li>
&lt;li>Bei Entity Recognition, wo wir Person, Company und Other für alles andere labeln könnten&lt;/li>
&lt;/ul>
&lt;p>Andere Male möchten wir unsere Eingabesequenz auf eine Vektorrepräsentation reduzieren und diese nutzen, um etwas über den gesamten Satz vorherzusagen.&lt;/p>
&lt;ul>
&lt;li>Wir könnten eine E-Mail als Spam labeln basierend auf ihrem Inhalt und/oder Betreff&lt;/li>
&lt;li>Vorhersagen, ob ein bestimmter Satz sarkastisch ist oder nicht&lt;/li>
&lt;/ul>
&lt;p>In diesen Fällen können wir den traditionellen Ansätzen der &lt;em>Vision-Leute&lt;/em> folgen und unser Netzwerk mit Convolution-Schichten abschließen, die kein Padding haben, und/oder Pooling-Operationen verwenden.&lt;/p>
&lt;p>Aber manchmal wollen wir dem Seq2Seq-Paradigma folgen, was &lt;a href="https://medium.com/u/42936aed59d2">Matthew Honnibal&lt;/a> prägnant &lt;a href="https://explosion.ai/blog/deep-learning-formula-nlp">&lt;em>Embed, encode, attend, predict&lt;/em>&lt;/a>&lt;em>.&lt;/em> genannt hat. In diesem Fall reduzieren wir unsere Eingabe auf eine Vektorrepräsentation, müssen diesen Vektor aber irgendwie wieder zu einer Sequenz der richtigen Länge hochsampeln.&lt;/p>
&lt;p>Diese Aufgabe bringt zwei Probleme mit sich&lt;/p>
&lt;ul>
&lt;li>Wie machen wir Upsampling mit Convolutions?&lt;/li>
&lt;li>Wie machen wir genau die richtige Menge an Upsampling?&lt;/li>
&lt;/ul>
&lt;p>Ich habe die Antwort auf die zweite Frage noch nicht gefunden oder zumindest noch nicht verstanden. In der Praxis hat es mir genügt, irgendeine obere Schranke für die maximale Länge der Ausgabe anzunehmen und dann bis zu diesem Punkt hochzusampeln. Ich vermute, Facebooks neues &lt;a href="https://s3.amazonaws.com/fairseq/papers/convolutional-sequence-to-sequence-learning.pdf">Translation-Paper&lt;/a> adressiert das, aber ich habe es noch nicht tief genug gelesen, um es zu kommentieren.&lt;/p>
&lt;h4 id="upsampling-mit-deconvolutions">Upsampling mit Deconvolutions&lt;/h4>
&lt;p>Deconvolutions sind unser Werkzeug für Upsampling. Am einfachsten (für mich) ist zu verstehen, was sie tun, durch Visualisierungen. Glücklicherweise haben ein paar kluge Leute einen &lt;a href="http://distill.pub/2016/deconv-checkerboard/">großartigen Post über Deconvolutions&lt;/a> bei Distill veröffentlicht und ein paar spaßige Visualizer eingebaut. Lass uns damit anfangen.&lt;/p>
&lt;p>&lt;img
src="./strided-convolution-diagram.webp"
alt="Diagramm einer gestrideten Convolution, das zeigt, wie der Kernel Eingaben abdeckt"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Betrachte das Bild oben. Wenn wir die untere Schicht als Eingabe nehmen, haben wir eine Standard-Convolution mit Stride 1 und Breite 3. &lt;em>Aber,&lt;/em> wir können auch von oben nach unten gehen, also die obere Schicht als Eingabe behandeln und die etwas größere untere Schicht erhalten.&lt;/p>
&lt;p>Wenn du darüber eine Sekunde nachdenkst: Diese „Top-down“-Operation passiert bereits in deinen Convolutional Networks, wenn du Backpropagation machst, da die Gradientensignale genau so propagieren müssen, wie im Bild gezeigt. Noch besser: Es stellt sich heraus, dass diese Operation einfach die Transponierte der Convolution-Operation ist, daher der andere verbreitete (und technisch korrekte) Name für diese Operation: Transposed Convolution.&lt;/p>
&lt;p>Jetzt wird’s spannend. Wir können unsere Convolutions striden, um unsere Eingabe zu verkleinern. Also können wir unsere Deconvolutions striden, um unsere Eingabe zu vergrößern. Ich denke, der einfachste Weg zu verstehen, wie Strides mit Deconvolutions funktionieren, ist, sich die folgenden Bilder anzusehen.&lt;/p>
&lt;p>&lt;img
src="./strided-convolution-diagram.webp"
alt="Diagramm einer gestrideten Convolution, das zeigt, wie der Kernel Eingaben abdeckt"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;img
src="./transposed-convolution-overlap.webp"
alt="Transposed Convolution mit überlappender Abdeckung der Ausgaben"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Das obere haben wir schon gesehen. Beachte, dass jede Eingabe (die obere Schicht) drei der Ausgaben speist und jede der Ausgaben von drei Eingaben gespeist wird (außer an den Rändern).&lt;/p>
&lt;p>&lt;img
src="./dilated-convolution-spacing.webp"
alt="Abstand einer dilatierten Convolution mit Lücken, die das rezeptive Feld erweitern"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Im zweiten Bild platzieren wir imaginäre Löcher in unseren Eingaben. Beachte, dass nun jede Ausgabe von höchstens zwei Eingaben gespeist wird.&lt;/p>
&lt;p>&lt;img
src="./transposed-convolution-upscaling.webp"
alt="Transposed Convolution, die eine Sequenzlänge hochskaliert"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Im dritten Bild haben wir zwei imaginäre Löcher in unsere Eingabeschicht eingefügt, und so wird jede Ausgabe von genau einer Eingabe gespeist. Das verdreifacht am Ende die Sequenzlänge unserer Ausgabe im Verhältnis zur Sequenzlänge unserer Eingabe.&lt;/p>
&lt;p>Schließlich können wir mehrere Deconvolution-Schichten stapeln, um unsere Ausgabeschicht schrittweise auf die gewünschte Größe anwachsen zu lassen.&lt;/p>
&lt;p>Ein paar Dinge, über die es sich lohnt nachzudenken&lt;/p>
&lt;ol>
&lt;li>Wenn du diese Zeichnungen von unten nach oben betrachtest, sind es am Ende Standard-Strided-Convolutions, bei denen wir einfach imaginäre Löcher in den Ausgabeschichten hinzugefügt haben (die weißen Blöcke)&lt;/li>
&lt;li>In der Praxis ist jede „Eingabe“ keine einzelne Zahl, sondern ein Vektor. In der Bildwelt könnte das ein 3-dimensionaler RGB-Wert sein. In Text könnte es ein 300-dimensionales Word Embedding sein. Wenn du in der Mitte deines Netzwerks (de)convolvierst, wäre jeder Punkt ein Vektor in der Größe, die aus der letzten Schicht herauskam.&lt;/li>
&lt;li>Ich erwähne das, um dich davon zu überzeugen, dass genug Information in der Eingabeschicht einer Deconvolution steckt, um sich über ein paar Punkte in der Ausgabe zu verteilen.&lt;/li>
&lt;li>In der Praxis hatte ich Erfolg damit, nach einer Deconvolution ein paar Convolutions mit längenerhaltendem Padding auszuführen. Ich stelle mir vor (habe es aber nicht bewiesen), dass das wie eine Umverteilung von Information wirkt. Ich denke daran wie daran, ein Steak nach dem Grillen ruhen zu lassen, damit sich die Säfte neu verteilen.&lt;/li>
&lt;/ol>
&lt;p>&lt;img
src="./steak-resting-comparison.webp"
alt="Vergleich von Steak ohne Ruhezeit versus mit Ruhezeit, um Umverteilung von Information zu veranschaulichen"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h3 id="zusammenfassung">Zusammenfassung&lt;/h3>
&lt;p>Der Hauptgrund, warum du Convolutions in deiner Arbeit in Betracht ziehen solltest, ist, dass sie schnell sind. Ich denke, das ist wichtig, um Forschung und Exploration schneller und effizienter zu machen. Schnellere Netzwerke verkürzen unsere Feedback-Zyklen.&lt;/p>
&lt;p>Die meisten Aufgaben, denen ich mit Text begegnet bin, haben am Ende dieselbe Anforderung an die Architektur: das rezeptive Feld zu maximieren, während ein ausreichender Fluss von Gradienten erhalten bleibt. Wir haben den Einsatz sowohl von DenseNets als auch von dilatierten Convolutions gesehen, um das zu erreichen.&lt;/p>
&lt;p>Schließlich wollen wir manchmal eine Sequenz oder einen Vektor zu einer größeren Sequenz erweitern. Wir haben Deconvolutions als eine Methode betrachtet, um „Upsampling“ auf Text zu machen, und als Bonus das Hinzufügen einer Convolution danach mit dem Ruhenlassen eines Steaks verglichen, damit es seine Säfte neu verteilt.&lt;/p>
&lt;p>Ich würde gerne mehr über deine Gedanken und Erfahrungen mit diesen Arten von Modellen erfahren. Teile sie in den Kommentaren oder ping mich auf Twitter &lt;a href="https://twitter.com/thetalperry">@thetalperry&lt;/a>&lt;/p></description><author/><guid>https://talperry.com/de/posts/classics/cmft/</guid><pubDate>Mon, 22 May 2017 00:00:00 +0000</pubDate></item><item><title>Deep Learning The Stock Market</title><link>https://talperry.com/de/posts/classics/dlsm/</link><description>&lt;p>&lt;em>&lt;strong>Update 15.03.2024&lt;/strong> Ich habe das vor mehr als sieben Jahren geschrieben. Mein Verständnis hat sich seitdem weiterentwickelt, und die Welt des Deep Learning hat seitdem mehr als eine Revolution durchlaufen. Damals war es populär und ist vielleicht immer noch eine unterhaltsame Lektüre, auch wenn du anderswo wahrscheinlich genauere und aktuellere Informationen findest&lt;/em>&lt;/p>
&lt;p>&lt;em>&lt;strong>Update 25.1.17&lt;/strong> — Hat eine Weile gedauert, aber&lt;/em> &lt;a href="https://github.com/talolard/MarketVectors/blob/master/preparedata.ipynb">&lt;em>hier ist ein ipython notebook&lt;/em>&lt;/a> &lt;em>mit einer groben Implementierung&lt;/em>&lt;/p>
&lt;p>&lt;img
src="./performance-plot-market-returns.webp"
alt="Vergleich der kumulierten Rendite für verschiedene Handelssignale"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h2 id="warum-nlp-für-aktienprognosen-relevant-ist">Warum NLP für Aktienprognosen relevant ist&lt;/h2>
&lt;p>In vielen NLP-Problemen nehmen wir am Ende eine Sequenz und kodieren sie in eine einzelne, fest große Repräsentation, und dekodieren diese Repräsentation dann in eine andere Sequenz. Zum Beispiel könnten wir Entitäten im Text markieren, von Englisch nach Französisch übersetzen oder Audiofrequenzen in Text umwandeln. In diesen Bereichen kommt eine Flut an Arbeiten heraus, und viele Ergebnisse erreichen State-of-the-Art-Performance.&lt;/p>
&lt;p>Meiner Ansicht nach ist der größte Unterschied zwischen NLP und Finanzanalyse, dass Sprache eine gewisse Garantie für Struktur hat – nur sind die Regeln dieser Struktur vage. Märkte hingegen kommen nicht mit dem Versprechen einer lernbaren Struktur; dass eine solche Struktur existiert, ist die Annahme, die dieses Projekt beweisen oder widerlegen würde (bzw. beweisen oder widerlegen könnte, wenn ich diese Struktur finde).&lt;/p>
&lt;p>Wenn wir annehmen, dass die Struktur da ist, erscheint mir die Idee plausibel, den aktuellen Zustand des Marktes so zusammenzufassen, wie wir die Semantik eines Absatzes kodieren. Wenn das noch keinen Sinn ergibt, lies weiter. Es wird.&lt;/p>
&lt;h2 id="man-soll-ein-wort-an-der-gesellschaft-erkennen-die-es-hält-firth-j-r-195711">Man soll ein Wort an der Gesellschaft erkennen, die es hält (Firth, J. R. 1957:11)&lt;/h2>
&lt;p>Es gibt jede Menge Literatur zu Word Embeddings. &lt;a href="https://www.youtube.com/watch?v=xhHOL3TNyJs&amp;index=2&amp;list=PLmImxx8Char9Ig0ZHSyTqGsdhb9weEGam">Richard Sochers Vortrag&lt;/a> ist ein großartiger Einstieg. Kurz gesagt: Wir können eine Geometrie aller Wörter in unserer Sprache konstruieren, und diese Geometrie erfasst die Bedeutung von Wörtern und die Beziehungen zwischen ihnen. Du hast vielleicht das Beispiel „King-man +woman=Queen“ oder so ähnlich gesehen.&lt;/p>
&lt;p>&lt;img
src="./shakespeare-code-sample.webp"
alt="Beispiel einer Embedding-Geometrie mit den nächsten Nachbarn für das Wort frog"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Embeddings sind cool, weil sie uns erlauben, Informationen in komprimierter Form darzustellen. Die alte Art, Wörter zu repräsentieren, war, einen Vektor (eine große Liste von Zahlen) zu halten, der so lang ist wie die Anzahl der Wörter, die wir kennen, und eine 1 an eine bestimmte Stelle zu setzen, wenn das das aktuelle Wort ist, das wir betrachten. Das ist weder effizient noch trägt es irgendeine Bedeutung. Mit Embeddings können wir alle Wörter in einer festen Anzahl an Dimensionen darstellen (300 scheinen völlig zu reichen, 50 funktionieren großartig) und dann ihre höherdimensionale Geometrie nutzen, um sie zu verstehen.&lt;/p>
&lt;p>Das Bild unten zeigt ein Beispiel. Ein Embedding wurde auf mehr oder weniger dem gesamten Internet trainiert. Nach ein paar Tagen intensiver Berechnungen wurde jedes Wort in einem hochdimensionalen Raum eingebettet. Dieser „Raum“ hat eine Geometrie, Konzepte wie Distanz, und so können wir fragen, welche Wörter nah beieinander liegen. Die Autoren/Erfinder dieser Methode haben ein Beispiel gemacht. Hier sind die Wörter, die am nächsten bei Frog liegen.&lt;/p>
&lt;p>&lt;img
src="./word2vec-neighbors-frog.webp"
alt="Liste der nächsten Nachbarn für das Wort frog aus einem word2vec-Modell"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Aber wir können mehr als nur Wörter einbetten. Wir können zum Beispiel Stock-Market-Embeddings machen.&lt;/p>
&lt;h2 id="market2vec">Market2Vec&lt;/h2>
&lt;p>Der erste Word-Embedding-Algorithmus, von dem ich gehört habe, war word2vec. Ich will denselben Effekt für den Markt erzielen, auch wenn ich einen anderen Algorithmus verwenden werde. Meine Eingangsdaten sind ein csv: Die erste Spalte ist das Datum, und es gibt 4*1000 Spalten, die den High-, Low-, Open- und Closing-Preis von 1000 Aktien entsprechen. Das heißt, mein Input-Vektor ist 4000-dimensional, was zu groß ist. Also werde ich als Erstes ihn in einen niedrigdimensionalen Raum stopfen, sagen wir 300, weil mir der Film gefallen hat.
&lt;img
src="./market-embedding-diagram.webp"
alt="Market2Vec-Embedding-Diagramm, das 4000-dimensionale Preise auf 300 komprimiert"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Etwas in 4000 Dimensionen zu nehmen und in einen 300-dimensionalen Raum zu stopfen klingt vielleicht schwierig, ist aber eigentlich einfach. Wir müssen nur Matrizen multiplizieren. Eine Matrix ist eine große Excel-Tabelle, die in jeder Zelle Zahlen hat und keine Formatierungsprobleme. Stell dir eine Excel-Tabelle mit 4000 Spalten und 300 Zeilen vor, und wenn wir sie im Grunde gegen den Vektor „knallen“, kommt ein neuer Vektor heraus, der nur noch Größe 300 hat. So hätte man es mir im Studium erklären sollen.&lt;/p>
&lt;p>Die „Fanciness“ beginnt hier: Wir werden die Zahlen in unserer Matrix zufällig initialisieren, und ein Teil des „Deep Learning“ besteht darin, diese Zahlen zu aktualisieren, sodass sich unsere Excel-Tabelle verändert. Schließlich wird diese Matrix (ich bleibe ab jetzt bei Matrix) Zahlen enthalten, die unseren ursprünglichen 4000-dimensionalen Vektor in eine prägnante 300-dimensionale Zusammenfassung von sich selbst überführen.&lt;/p>
&lt;p>Wir werden hier noch ein bisschen schicker und wenden das an, was man eine Aktivierungsfunktion nennt. Wir nehmen eine Funktion und wenden sie auf jede Zahl im Vektor einzeln an, sodass am Ende alles zwischen 0 und 1 liegt (oder zwischen 0 und unendlich, je nachdem). Warum? Es macht unseren Vektor „besonderer“ und macht unseren Lernprozess in der Lage, kompliziertere Dinge zu verstehen. &lt;a href="https://lmgtfy.com/?q=why+does+deep+learning+use+non+linearities">Wie&lt;/a>?&lt;/p>
&lt;p>Na und? Was ich erwarte, ist, dass das neue Embedding der Marktpreise (der Vektor) in einen kleineren Raum alle wesentlichen Informationen für die jeweilige Aufgabe erfasst, ohne Zeit mit dem anderen Kram zu verschwenden. Ich würde also erwarten, dass es Korrelationen zwischen Aktien erfasst, vielleicht erkennt, wenn ein bestimmter Sektor fällt oder wenn der Markt sehr heiß läuft. Ich weiß nicht, welche Merkmale es finden wird, aber ich nehme an, dass sie nützlich sein werden.&lt;/p>
&lt;h2 id="und-jetzt">Und jetzt?&lt;/h2>
&lt;p>Lass uns unsere Marktvektoren für einen Moment beiseitelegen und über Sprachmodelle sprechen. &lt;a href="https://medium.com/u/ac9d9a35533e">Andrej Karpathy&lt;/a> schrieb den epischen Beitrag „&lt;a href="http://karpathy.github.io/2015/05/21/rnn-effectiveness/">The Unreasonable effectiveness of Recurrent Neural Networks&lt;/a>“. Wenn ich den Beitrag sehr großzügig zusammenfassen würde, läuft er auf Folgendes hinaus:&lt;/p>
&lt;ol>
&lt;li>Wenn wir uns die Werke Shakespeares anschauen und sie Zeichen für Zeichen durchgehen, können wir mit „Deep Learning“ ein Sprachmodell lernen.&lt;/li>
&lt;li>Ein Sprachmodell (in diesem Fall) &lt;strong>ist eine magische Box&lt;/strong>. Du gibst die ersten paar Zeichen hinein, und es sagt dir, was als Nächstes kommt.&lt;/li>
&lt;li>Wenn wir das Zeichen, das das Sprachmodell vorhergesagt hat, nehmen und wieder hineinfüttern, können wir ewig weitermachen.&lt;/li>
&lt;/ol>
&lt;p>Und dann, als Pointe, generierte er einen Haufen Text, der wie Shakespeare aussieht. Und dann tat er es noch einmal mit dem Linux-Quellcode. Und dann wieder mit einem Lehrbuch über algebraische Geometrie.&lt;/p>
&lt;p>Ich komme gleich wieder zur Mechanik dieser magischen Box zurück, aber lass mich dich daran erinnern, dass wir die zukünftige Marktentwicklung auf Basis der Vergangenheit vorhersagen wollen – genau so, wie er das nächste Wort auf Basis des vorherigen vorhergesagt hat. Wo Karpathy Zeichen benutzt hat, werden wir unsere Marktvektoren verwenden und sie in die magische schwarze Box füttern. Wir haben noch nicht entschieden, was sie vorhersagen soll, aber das ist okay; wir werden ihre Ausgabe auch nicht wieder zurück in sie einspeisen.&lt;/p>
&lt;h2 id="tiefer-gehen">Tiefer gehen&lt;/h2>
&lt;p>Ich möchte darauf hinweisen, dass wir hier in den „deep“-Teil von Deep Learning einsteigen. Bisher haben wir nur eine einzelne Schicht Lernen: diese Excel-Tabelle, die den Markt kondensiert. Jetzt werden wir ein paar weitere Schichten hinzufügen und sie stapeln, um etwas „Tiefes“ zu bauen. Das ist das „deep“ in Deep Learning.&lt;/p>
&lt;p>Karpathy zeigt uns einige Beispielausgaben aus dem Linux-Quellcode – das ist Zeug, das seine schwarze Box geschrieben hat.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">action_new_function&lt;/span>(&lt;span style="color:#66d9ef">struct&lt;/span> &lt;span style="color:#a6e22e">s_stat_info&lt;/span> &lt;span style="color:#f92672">*&lt;/span>wb)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">long&lt;/span> flags;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> lel_idx_bit &lt;span style="color:#f92672">=&lt;/span> e&lt;span style="color:#f92672">-&amp;gt;&lt;/span>edd, &lt;span style="color:#f92672">*&lt;/span>sys &lt;span style="color:#f92672">&amp;amp;&lt;/span> &lt;span style="color:#f92672">~&lt;/span>((&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">long&lt;/span>) &lt;span style="color:#f92672">*&lt;/span>FIRST_COMPAT);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> buf[&lt;span style="color:#ae81ff">0&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0xFFFFFFFF&lt;/span> &lt;span style="color:#f92672">&amp;amp;&lt;/span> (bit &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">4&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> min(inc, slist&lt;span style="color:#f92672">-&amp;gt;&lt;/span>bytes);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> printk(KERN_WARNING &lt;span style="color:#e6db74">&amp;#34;Memory allocated %02x/%02x, &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;original MLL instead&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> min(min(multi_run &lt;span style="color:#f92672">-&lt;/span> s&lt;span style="color:#f92672">-&amp;gt;&lt;/span>len, max) &lt;span style="color:#f92672">*&lt;/span> num_data_in),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> frame_pos, sz &lt;span style="color:#f92672">+&lt;/span> first_seg);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> div_u64_w(val, inb_p);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> spin_unlock(&lt;span style="color:#f92672">&amp;amp;&lt;/span>disk&lt;span style="color:#f92672">-&amp;gt;&lt;/span>queue_lock);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mutex_unlock(&lt;span style="color:#f92672">&amp;amp;&lt;/span>s&lt;span style="color:#f92672">-&amp;gt;&lt;/span>sock&lt;span style="color:#f92672">-&amp;gt;&lt;/span>mutex);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mutex_unlock(&lt;span style="color:#f92672">&amp;amp;&lt;/span>func&lt;span style="color:#f92672">-&amp;gt;&lt;/span>mutex);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> disassemble(info&lt;span style="color:#f92672">-&amp;gt;&lt;/span>pending_bh);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Beachte, dass es weiß, wie man Klammern öffnet und schließt und Einrückungskonventionen respektiert; der Inhalt der Funktion ist korrekt eingerückt und die mehrzeilige &lt;em>printk&lt;/em>-Anweisung hat eine innere Einrückung. Das bedeutet, dass diese magische Box Langstreckenabhängigkeiten versteht. Wenn sie innerhalb der print-Anweisung einrückt, weiß sie, dass sie in einer print-Anweisung ist, und erinnert sich außerdem daran, dass sie in einer Funktion ist (oder zumindest in einem anderen eingerückten Scope). &lt;strong>Das ist verrückt.&lt;/strong> Man übersieht das leicht, aber ein Algorithmus, der langfristige Abhängigkeiten erfassen und behalten kann, ist super nützlich, weil… wir langfristige Abhängigkeiten im Markt finden wollen.&lt;/p>
&lt;h2 id="in-der-magischen-schwarzen-box">In der magischen schwarzen Box&lt;/h2>
&lt;p>Was ist in dieser magischen schwarzen Box? Es ist eine Art Recurrent Neural Network (RNN) namens LSTM. Ein RNN ist ein Deep-Learning-Algorithmus, der auf Sequenzen arbeitet (wie Sequenzen von Zeichen). Bei jedem Schritt nimmt es eine Repräsentation des nächsten Zeichens (wie die Embeddings, über die wir zuvor gesprochen haben) und verarbeitet diese Repräsentation mit einer Matrix, wie wir es schon gesehen haben. Der Punkt ist: Das RNN hat eine Form von internem Gedächtnis, also merkt es sich, was es zuvor gesehen hat. Es nutzt dieses Gedächtnis, um zu entscheiden, wie genau es auf den nächsten Input operieren soll. Mit diesem Gedächtnis kann das RNN „sich merken“, dass es sich in einem eingerückten Scope befindet – und so erhalten wir korrekt verschachtelten Ausgabetext.&lt;/p>
&lt;p>&lt;img
src="./nested-scope-code-structure.webp"
alt="Entfaltetes LSTM über die Zeit, das zeigt, wie der Hidden State Einrückungskontext trägt"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Eine fancy Version eines RNN nennt man Long Short Term Memory (LSTM). LSTM hat clever konstruiertes Gedächtnis, das es ihm erlaubt:&lt;/p>
&lt;ol>
&lt;li>Selektiv zu wählen, was es sich merkt&lt;/li>
&lt;li>Zu entscheiden, zu vergessen&lt;/li>
&lt;li>Auszuwählen, wie viel seines Gedächtnisses es ausgeben soll.&lt;/li>
&lt;/ol>
&lt;p>&lt;img
src="./lstm-memory-gates.webp"
alt="Diagramm der LSTM-Gates, die Memory-Input-, Output- und Forget-Operationen steuern"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Ein LSTM kann also ein „{“ sehen und sich sagen: „Oh ja, das ist wichtig, das sollte ich mir merken“, und wenn es das tut, merkt es sich im Wesentlichen einen Hinweis darauf, dass es sich in einem verschachtelten Scope befindet. Sobald es die entsprechende „}“ sieht, kann es entscheiden, die ursprüngliche öffnende Klammer zu vergessen – und damit vergessen, dass es in einem verschachtelten Scope ist.&lt;/p>
&lt;p>Wir können dem LSTM beibringen, abstraktere Konzepte zu lernen, indem wir mehrere davon übereinander stapeln – damit wären wir wieder „Deep“. Nun wird jede Ausgabe des vorherigen LSTM zum Input des nächsten LSTM, und jedes lernt höhere Abstraktionen der eingehenden Daten. Im Beispiel oben (und das ist nur illustrative Spekulation) könnte die erste LSTM-Schicht lernen, dass Zeichen, die durch ein Leerzeichen getrennt sind, „Wörter“ sind. Die nächste Schicht könnte Worttypen lernen wie (&lt;code>**static** **void** **action_new_function).**&lt;/code>Die nächste Schicht könnte das Konzept einer Funktion und ihrer Argumente lernen und so weiter. Es ist schwer zu sagen, was jede Schicht genau macht, aber Karpathys Blog hat ein wirklich schönes Beispiel dafür, wie er genau das visualisiert hat.&lt;/p>
&lt;h2 id="market2vec-und-lstms-verbinden">Market2Vec und LSTMs verbinden&lt;/h2>
&lt;p>Der fleißige Leser wird bemerken, dass Karpathy Zeichen als Inputs verwendet hat, nicht Embeddings (technisch gesehen ein One-Hot-Encoding von Zeichen). Aber Lars Eidnes hat tatsächlich Word Embeddings verwendet, als er &lt;a href="https://larseidnes.com/2015/10/13/auto-generating-clickbait-with-recurrent-neural-networks/">Auto-Generating Clickbait With Recurrent Neural Network&lt;/a> schrieb.&lt;/p>
&lt;p>&lt;img
src="./stacked-lstm-architecture.webp"
alt="Gestapelte LSTM-Architektur, die Wortvektoren konsumiert und Ausgaben nach oben weitergibt"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Die Abbildung oben zeigt das Netzwerk, das er benutzt hat. Ignoriere den SoftMax-Teil (dazu kommen wir später). Für den Moment schau dir an, wie er unten eine Sequenz von Wortvektoren einspeist und jeden einzelnen. (Zur Erinnerung: Ein „Wortvektor“ ist eine Repräsentation eines Wortes in Form einer Reihe von Zahlen, wie wir es am Anfang dieses Posts gesehen haben.) Lars gibt eine Sequenz von Wortvektoren ein, und jeder von ihnen:&lt;/p>
&lt;ol>
&lt;li>Beeinflusst das erste LSTM&lt;/li>
&lt;li>Lässt sein LSTM etwas an das LSTM darüber ausgeben&lt;/li>
&lt;li>Lässt sein LSTM etwas an das LSTM für das nächste Wort ausgeben&lt;/li>
&lt;/ol>
&lt;p>Wir werden dasselbe machen, mit einem Unterschied: Statt Wortvektoren geben wir „MarketVectors“ ein – diese MarketVectors, die wir zuvor beschrieben haben. Zur Wiederholung: Die MarketVectors sollten eine Zusammenfassung dessen enthalten, was zu einem bestimmten Zeitpunkt am Markt passiert. Indem ich eine Sequenz davon durch LSTMs schicke, hoffe ich, die langfristige Dynamik zu erfassen, die im Markt stattgefunden hat. Indem ich mehrere LSTM-Schichten stapel, hoffe ich, höherstufige Abstraktionen des Marktverhaltens zu erfassen.&lt;/p>
&lt;h2 id="was-kommt-heraus">Was kommt heraus&lt;/h2>
&lt;p>&lt;em>An dieser Stelle haben wir noch überhaupt nicht darüber gesprochen, wie der Algorithmus eigentlich irgendetwas lernt; wir haben nur über all die cleveren Transformationen gesprochen, die wir mit den Daten machen. Diese Diskussion verschieben wir ein paar Absätze nach unten, aber behalte diesen Teil bitte im Kopf, denn er ist das Setup für die Pointe, die alles andere lohnenswert macht.&lt;/em>&lt;/p>
&lt;p>In Karpathys Beispiel ist die Ausgabe der LSTMs ein Vektor, der das nächste Zeichen in einer abstrakten Repräsentation darstellt. In Eidnes’ Beispiel ist die Ausgabe der LSTMs ein Vektor, der darstellt, was das nächste Wort in einem abstrakten Raum sein wird. Der nächste Schritt ist in beiden Fällen, diese abstrakte Repräsentation in einen Wahrscheinlichkeitsvektor zu verwandeln – also eine Liste, die sagt, wie wahrscheinlich es ist, dass jeweils das nächste Zeichen bzw. Wort erscheint. Das ist die Aufgabe der SoftMax-Funktion. Sobald wir eine Liste von Wahrscheinlichkeiten haben, wählen wir das Zeichen oder Wort, das am wahrscheinlichsten als Nächstes erscheint.&lt;/p>
&lt;p>In unserem Fall, beim „Vorhersagen des Marktes“, müssen wir uns fragen, was genau der Markt vorhersagen soll. Einige Optionen, über die ich nachgedacht habe, waren:&lt;/p>
&lt;ol>
&lt;li>Den nächsten Preis für jede der 1000 Aktien vorhersagen&lt;/li>
&lt;li>Den Wert eines Index (S&amp;amp;P, VIX etc.) in den nächsten &lt;em>n&lt;/em> Minuten vorhersagen.&lt;/li>
&lt;li>Vorhersagen, welche Aktien in den nächsten &lt;em>n&lt;/em> Minuten um mehr als &lt;em>x%&lt;/em> steigen werden&lt;/li>
&lt;li>(Mein persönlicher Favorit) Vorhersagen, welche Aktien in den nächsten &lt;em>n&lt;/em> Minuten um &lt;em>2x%&lt;/em> steigen/fallen werden, ohne in dieser Zeit um mehr als &lt;em>x%&lt;/em> zu fallen/steigen.&lt;/li>
&lt;li>(Dem wir im Rest dieses Artikels folgen). Vorhersagen, wann der VIX in den nächsten &lt;em>n&lt;/em> Minuten um &lt;em>2x%&lt;/em> steigt/fällt, ohne in dieser Zeit um mehr als &lt;em>x%&lt;/em> zu fallen/steigen.&lt;/li>
&lt;/ol>
&lt;p>1 und 2 sind Regressionsprobleme, bei denen wir eine tatsächliche Zahl vorhersagen müssen statt der Wahrscheinlichkeit eines bestimmten Ereignisses (wie dass der Buchstabe n erscheint oder der Markt steigt). Die sind okay, aber nicht das, was ich machen will.&lt;/p>
&lt;p>3 und 4 sind ziemlich ähnlich: Beide fragen nach der Vorhersage eines Ereignisses (im Fachjargon — eines Klassenlabels). Ein Ereignis könnte sein, dass als Nächstes der Buchstabe &lt;em>n&lt;/em> erscheint, oder: &lt;em>Ist um 5% gestiegen, ohne in den letzten 10 Minuten mehr als 3% zu fallen.&lt;/em> Der Trade-off zwischen 3 und 4 ist, dass 3 viel häufiger ist und daher leichter zu lernen, während 4 wertvoller ist, weil es nicht nur ein Indikator für Profit ist, sondern auch eine Risikobedingung enthält.&lt;/p>
&lt;p>5 ist das, womit wir für diesen Artikel weitermachen, weil es ähnlich zu 3 und 4 ist, aber Mechaniken hat, denen leichter zu folgen ist. Der &lt;a href="https://en.wikipedia.org/wiki/VIX">VIX&lt;/a> wird manchmal Fear Index genannt und repräsentiert, wie volatil die Aktien im S&amp;amp;P500 sind. Er wird abgeleitet, indem man die &lt;a href="https://en.wikipedia.org/wiki/Implied_volatility">implizite Volatilität&lt;/a> für bestimmte Optionen auf jede Aktie im Index beobachtet.&lt;/p>
&lt;h3 id="randnotiz--warum-den-vix-vorhersagen">Randnotiz — Warum den VIX vorhersagen&lt;/h3>
&lt;p>Was den VIX zu einem interessanten Ziel macht, ist:&lt;/p>
&lt;ol>
&lt;li>Es ist nur eine Zahl im Gegensatz zu 1000en Aktien. Das macht es konzeptionell leichter nachzuvollziehen und reduziert Rechenkosten.&lt;/li>
&lt;li>Er ist die Zusammenfassung vieler Aktien, also sind die meisten, wenn nicht alle unserer Inputs relevant&lt;/li>
&lt;li>Er ist keine lineare Kombination unserer Inputs. Implizite Volatilität wird aktienweise aus einer komplizierten, nichtlinearen Formel extrahiert. Der VIX wird aus einer komplexen Formel darauf aufbauend abgeleitet. Wenn wir das vorhersagen können, ist das ziemlich cool.&lt;/li>
&lt;li>Er ist handelbar, also können wir ihn nutzen, falls das tatsächlich funktioniert.&lt;/li>
&lt;/ol>
&lt;h2 id="zurück-zu-unseren-lstm-ausgaben-und-softmax">Zurück zu unseren LSTM-Ausgaben und SoftMax&lt;/h2>
&lt;p>Wie nutzen wir die Formulierungen, die wir zuvor gesehen haben, um Änderungen im VIX ein paar Minuten in der Zukunft vorherzusagen? Für jeden Punkt in unserem Datensatz schauen wir, was 5 Minuten später mit dem VIX passiert ist. Wenn er um mehr als 1% gestiegen ist, ohne in dieser Zeit um mehr als 0,5% zu fallen, geben wir eine 1 aus, sonst eine 0. Dann erhalten wir eine Sequenz, die so aussieht:&lt;/p>
&lt;blockquote>
&lt;p>0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,0,1,1,1,0,0,0,0,0 ….&lt;/p>
&lt;/blockquote>
&lt;p>Wir wollen den Vektor, den unsere LSTMs ausgeben, so „quetschen“, dass er uns die Wahrscheinlichkeit liefert, dass der nächste Eintrag in unserer Sequenz eine 1 ist. Dieses Quetschen passiert im SoftMax-Teil des Diagramms oben. (Technisch gesehen, da wir jetzt nur 1 Klasse haben, verwenden wir eine Sigmoid-Funktion).&lt;/p>
&lt;p>Bevor wir also dazu kommen, wie das Ding lernt, fassen wir zusammen, was wir bisher gemacht haben:&lt;/p>
&lt;ol>
&lt;li>Wir nehmen als Input eine Sequenz von Preisdaten für 1000 Aktien&lt;/li>
&lt;li>Jeder Zeitpunkt in der Sequenz ist ein Snapshot des Marktes. Unser Input ist eine Liste von 4000 Zahlen. Wir verwenden eine Embedding-Schicht, um die Schlüsselinformationen in nur 300 Zahlen zu repräsentieren.&lt;/li>
&lt;li>Jetzt haben wir eine Sequenz von Embeddings des Marktes. Wir geben diese in einen Stack von LSTMs, Timestep für Timestep. Die LSTMs erinnern sich an Dinge aus den vorherigen Schritten, und das beeinflusst, wie sie den aktuellen verarbeiten.&lt;/li>
&lt;li>Wir geben die Ausgabe der ersten LSTM-Schicht in eine weitere Schicht. Diese erinnern sich ebenfalls, und sie lernen höherstufige Abstraktionen der Informationen, die wir hineinstecken.&lt;/li>
&lt;li>Schließlich nehmen wir die Ausgaben aus allen LSTMs und „quetschen sie“, sodass unsere Sequenz von Marktinformationen zu einer Sequenz von Wahrscheinlichkeiten wird. Die Wahrscheinlichkeit ist: „Wie wahrscheinlich ist es, dass der VIX in den nächsten 5 Minuten um 1% steigt, ohne um 0,5% zu fallen?“&lt;/li>
&lt;/ol>
&lt;h2 id="wie-lernt-dieses-ding">Wie lernt dieses Ding?&lt;/h2>
&lt;p>Jetzt kommt der spaßige Teil. Alles, was wir bis jetzt gemacht haben, nennt man den Forward Pass: Wir würden all diese Schritte machen, während wir den Algorithmus trainieren, und auch wenn wir ihn in Produktion verwenden. Hier sprechen wir über den Backward Pass – den Teil, den wir nur während des Trainings machen und der unseren Algorithmus lernen lässt.&lt;/p>
&lt;p>Während des Trainings haben wir nicht nur jahrelange historische Daten vorbereitet, sondern auch eine Sequenz von Vorhersagezielen – diese Liste aus 0 und 1, die zeigte, ob sich der VIX nach jeder Beobachtung in unseren Daten so bewegt hat, wie wir es wollen.&lt;/p>
&lt;p>Um zu lernen, füttern wir die Marktdaten in unser Netzwerk und vergleichen seine Ausgabe mit dem, was wir berechnet haben. In unserem Fall wird der Vergleich einfache Subtraktion sein; das heißt, wir sagen, dass der Fehler unseres Modells ist:&lt;/p>
&lt;blockquote>
&lt;p>ERROR = (((precomputed)— (predicted probability))² )^(1/2)&lt;/p>
&lt;/blockquote>
&lt;p>Oder auf Deutsch: die Quadratwurzel aus dem Quadrat der Differenz zwischen dem, was tatsächlich passiert ist, und dem, was wir vorhergesagt haben.&lt;/p>
&lt;p>Hier ist das Schöne: Das ist eine differenzierbare Funktion; das heißt, wir können sagen, um wie viel sich der Fehler geändert hätte, wenn sich unsere Vorhersage ein wenig geändert hätte. Unsere Vorhersage ist das Ergebnis einer differenzierbaren Funktion – dem SoftMax. Die Inputs in den Softmax, die LSTMs, sind alles mathematische Funktionen, die differenzierbar sind. Nun sind all diese Funktionen voller Parameter – diese großen Excel-Tabellen, von denen ich vor Ewigkeiten gesprochen habe. In diesem Stadium nehmen wir also die Ableitung des Fehlers nach jedem einzelnen der Millionen Parameter in all diesen Excel-Tabellen in unserem Modell. Wenn wir das tun, können wir sehen, wie sich der Fehler ändert, wenn wir jeden Parameter ändern, also ändern wir jeden Parameter so, dass der Fehler sinkt.&lt;/p>
&lt;p>Dieses Verfahren propagiert sich bis ganz zum Anfang des Modells. Es optimiert die Art, wie wir die Inputs in MarketVectors einbetten, sodass unsere MarketVectors die bedeutendsten Informationen für unsere Aufgabe repräsentieren.&lt;/p>
&lt;p>Es optimiert, wann und was jedes LSTM sich merkt, sodass ihre Ausgaben für unsere Aufgabe am relevantesten sind.&lt;/p>
&lt;p>Es optimiert die Abstraktionen, die unsere LSTMs lernen, sodass sie die wichtigsten Abstraktionen für unsere Aufgabe lernen.&lt;/p>
&lt;p>Was ich erstaunlich finde, weil wir all diese Komplexität und Abstraktion haben, die wir nirgends explizit spezifizieren mussten. Das wird alles MathaMagically aus der Spezifikation dessen abgeleitet, was wir als Fehler betrachten.&lt;/p>
&lt;p>&lt;img
src="./stochastic-gradient-plot.webp"
alt="Trainings-Loss-Kurve, die das Verhalten von Stochastic Gradient Descent illustriert"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h2 id="was-kommt-als-nächstes">Was kommt als Nächstes&lt;/h2>
&lt;p>Jetzt, da ich das schriftlich ausgearbeitet habe und es für mich immer noch Sinn ergibt, möchte ich:&lt;/p>
&lt;ol>
&lt;li>Sehen, ob sich überhaupt jemand die Mühe macht, das zu lesen.&lt;/li>
&lt;li>Alle Fehler korrigieren, auf die meine lieben Leser hinweisen&lt;/li>
&lt;li>Überlegen, ob das noch machbar ist&lt;/li>
&lt;li>Und es bauen&lt;/li>
&lt;/ol>
&lt;p>Also: Wenn du bis hierher gekommen bist, bitte weise auf meine Fehler hin und teile deine Inputs.&lt;/p>
&lt;h2 id="andere-gedanken">Andere Gedanken&lt;/h2>
&lt;p>Hier sind einige überwiegend fortgeschrittenere Gedanken zu diesem Projekt – welche anderen Dinge ich ausprobieren könnte und warum es mir sinnvoll erscheint, dass das tatsächlich funktionieren könnte.&lt;/p>
&lt;h3 id="liquidität-und-effiziente-nutzung-von-kapital">Liquidität und effiziente Nutzung von Kapital&lt;/h3>
&lt;p>Im Allgemeinen gilt: Je liquider ein bestimmter Markt ist, desto effizienter ist er. Ich denke, das liegt an einem Henne-und-Ei-Zyklus: Wenn ein Markt liquider wird, kann er mehr Kapital aufnehmen, das hinein- und herausfließt, ohne dass dieses Kapital sich selbst schadet. Wenn ein Markt liquider wird und mehr Kapital darin eingesetzt werden kann, wirst du mehr anspruchsvolle (sophisticated) Akteure sehen, die einsteigen. Das liegt daran, dass es teuer ist, anspruchsvoll zu sein, also musst du Renditen auf einem großen Kapitalblock erzielen, um deine operativen Kosten zu rechtfertigen.&lt;/p>
&lt;p>Eine schnelle Folgerung ist, dass in weniger liquiden Märkten die Konkurrenz nicht ganz so anspruchsvoll ist, und daher könnten die Chancen, die ein System wie dieses bringt, noch nicht weg-arbitragiert worden sein. Der Punkt ist: Wenn ich versuchen würde, damit zu handeln, würde ich versuchen, es in weniger liquiden Segmenten des Marktes zu handeln – also vielleicht den TASE 100 statt den S&amp;amp;P 500.&lt;/p>
&lt;h3 id="das-zeug-ist-neu">Das Zeug ist neu&lt;/h3>
&lt;p>Das Wissen über diese Algorithmen, die Frameworks zu ihrer Ausführung und die Rechenleistung, um sie zu trainieren, sind alle neu – zumindest insofern, als sie dem durchschnittlichen Joe wie mir zur Verfügung stehen. Ich würde annehmen, dass die Top-Player das vor Jahren herausgefunden haben und auch schon lange die Kapazität hatten, es auszuführen; aber wie ich im Absatz oben erwähnt habe, agieren sie wahrscheinlich in liquiden Märkten, die ihre Größe tragen können. Die nächste Stufe von Marktteilnehmern, nehme ich an, hat eine geringere Geschwindigkeit der technologischen Assimilation, und in diesem Sinne gibt es – oder wird es bald geben – ein Rennen, das in bisher unerschlossenen Märkten umzusetzen.&lt;/p>
&lt;h3 id="mehrere-zeitrahmen">Mehrere Zeitrahmen&lt;/h3>
&lt;p>Obwohl ich oben einen einzelnen Input-Stream erwähnt habe, stelle ich mir vor, dass ein effizienterer Trainingsweg wäre, Marktvektoren (mindestens) auf mehreren Zeitrahmen zu trainieren und sie in der Inferenzphase einzuspeisen. Das heißt: Mein niedrigster Zeitrahmen wäre alle 30 Sekunden gesampelt, und ich würde erwarten, dass das Netzwerk Abhängigkeiten lernt, die sich höchstens über Stunden erstrecken.&lt;/p>
&lt;p>Ich weiß nicht, ob sie relevant sind oder nicht, aber ich denke, es gibt Muster auf mehreren Zeitrahmen, und wenn die Rechenkosten niedrig genug gebracht werden können, lohnt es sich, sie ins Modell zu integrieren. Ich ringe noch damit, wie man diese am besten im Computational Graph repräsentiert, und vielleicht ist es nicht zwingend nötig, damit anzufangen.&lt;/p>
&lt;h3 id="marketvectors">MarketVectors&lt;/h3>
&lt;p>Wenn wir Wortvektoren in NLP verwenden, starten wir normalerweise mit einem vortrainierten Modell und passen die Embeddings während des Trainings unseres Modells weiter an. In meinem Fall gibt es keine vortrainierten Marktvektoren, noch gibt es einen klaren Algorithmus, um sie zu trainieren.&lt;/p>
&lt;p>Meine ursprüngliche Überlegung war, einen Auto-Encoder zu verwenden, wie in &lt;a href="http://cs229.stanford.edu/proj2013/TakeuchiLee-ApplyingDeepLearningToEnhanceMomentumTradingStrategiesInStocks.pdf">diesem Paper&lt;/a>, aber End-to-End-Training ist cooler.&lt;/p>
&lt;p>Eine ernsthaftere Überlegung ist der Erfolg von Sequence-to-Sequence-Modellen in Übersetzung und Spracherkennung, bei denen eine Sequenz schließlich als einzelner Vektor kodiert und dann in eine andere Repräsentation dekodiert wird (wie von Sprache zu Text oder von Englisch zu Französisch). In dieser Sicht ist die gesamte Architektur, die ich beschrieben habe, im Wesentlichen der Encoder, und ich habe keinen Decoder wirklich ausgearbeitet.&lt;/p>
&lt;p>Aber ich will mit der ersten Schicht etwas Spezifisches erreichen – der Schicht, die den 4000-dimensionalen Vektor als Input nimmt und einen 300-dimensionalen ausgibt. Ich will, dass sie Korrelationen oder Beziehungen zwischen verschiedenen Aktien findet und Features darüber zusammensetzt.&lt;/p>
&lt;p>Die Alternative wäre, jeden Input durch ein LSTM zu schicken, vielleicht alle Output-Vektoren zu konkatenieren und das als Output der Encoder-Phase zu betrachten. Ich denke, das wäre ineffizient, weil die Interaktionen und Korrelationen zwischen Instrumenten und ihren Features verloren gehen würden, und es würde 10x mehr Rechenaufwand erfordern. Andererseits könnte so eine Architektur naiv über mehrere GPUs und Hosts parallelisiert werden, was ein Vorteil ist.&lt;/p>
&lt;h3 id="cnns">CNNs&lt;/h3>
&lt;p>In letzter Zeit gab es einen Schub an Papers zu zeichenbasierter maschineller Übersetzung. Dieses &lt;a href="https://arxiv.org/pdf/1610.03017v2.pdf">Paper&lt;/a> ist mir ins Auge gefallen, weil sie Langstreckenabhängigkeiten mit einer Convolutional-Schicht statt einem RNN erfassen. Ich habe es nur kurz gelesen, aber ich denke, eine Modifikation, bei der ich jede Aktie als Kanal behandle und zuerst über Kanäle falte (wie bei RGB-Bildern), wäre eine weitere Möglichkeit, die Marktdynamik zu erfassen – auf ähnliche Weise, wie sie im Wesentlichen semantische Bedeutung aus Zeichen kodieren.&lt;/p></description><author/><guid>https://talperry.com/de/posts/classics/dlsm/</guid><pubDate>Sat, 03 Dec 2016 00:00:00 +0000</pubDate></item></channel></rss>