Connect with us

Entwicklung & Code

KI macht Entwickler ersetzbar: Wer künftig gebraucht wird – und wer nicht


Dieses Thema richtet sich an zwei ganz unterschiedliche Zielgruppen: zum einen an Sie, wenn Sie Entwicklerin oder Entwickler sind, weil es darüber entscheidet, ob Sie mittel- bis langfristig Ihren Arbeitsplatz behalten. Zum anderen jedoch auch an Sie, wenn Sie Unternehmerin, Unternehmer oder eine Führungskraft sind, weil ich Ihnen in diesem Beitrag erklären werde, wie Sie Ihr Unternehmen und Ihre Teams künftig deutlich effizienter aufstellen können.


the next big thing – Golo Roden

the next big thing – Golo Roden

Golo Roden ist Gründer und CTO von the native web GmbH. Er beschäftigt sich mit der Konzeption und Entwicklung von Web- und Cloud-Anwendungen sowie -APIs, mit einem Schwerpunkt auf Event-getriebenen und Service-basierten verteilten Architekturen. Sein Leitsatz lautet, dass Softwareentwicklung kein Selbstzweck ist, sondern immer einer zugrundeliegenden Fachlichkeit folgen muss.

Empfohlener redaktioneller Inhalt

Mit Ihrer Zustimmung wird hier ein externes YouTube-Video (Google Ireland Limited) geladen.

Der perfekte neue Entwickler // deutsch

Das klingt dramatisch? Das ist es auch. Denn was sich in den letzten Monaten bei künstlicher Intelligenz entwickelt hat, wird die Softwareentwicklung wesentlich radikaler verändern, als die meisten derzeit ahnen. Bevor Sie nun denken: „Das ist doch nur der nächste Hype“, überlegen Sie bitte, wie es damals mit dem Internet war. Oder mit mobilen Apps. Oder mit der Cloud. Alle, die das damals ignoriert haben, gibt es heute praktisch nicht mehr. Diese Unternehmen haben den Anschluss verpasst. Genau dasselbe geschieht jetzt mit KI – nur in deutlich stärkerer Form.

Warum formuliere ich das so drastisch? Ganz einfach: Wir sind gerade dabei, die Art und Weise, wie wir Software entwickeln, vollständig umzubauen. Die meisten haben das bislang jedoch noch gar nicht richtig bemerkt.

Ein Beispiel: GitHub hat vor Kurzem etwas vorgestellt, das für viele Entwicklerinnen und Entwickler – ebenso wie für viele Unternehmen – ein absoluter Gamechanger ist. Sie können dort nun ein Issue nicht mehr nur einer Person zuweisen, sondern auch direkt an die GitHub-eigene KI namens Copilot. Copilot nimmt sich dann dieses Issue, arbeitet komplett eigenständig im Hintergrund und meldet sich nach 10 bis 30 Minuten mit einem fertigen Pull Request zurück. Darin befindet sich der neue Code, inklusive angepasster und erweiterter Tests, ebenso wie die aktualisierte Dokumentation. Alles sogar gelintet, formatiert und lauffähig. Einfach so.

Überlegen Sie sich das einmal: Früher hätten Sie eine solche Aufgabe vielleicht einer Junior-Entwicklerin oder einem Junior-Entwickler gegeben. Diese Person hätte dann zwei Tage daran gearbeitet, anschließend hätte es einen Review gegeben, danach Anpassungen – das Ganze wäre in mehreren Iterationen abgelaufen. Heute erledigt Copilot das innerhalb von 20 Minuten. Copilot wird dabei weder müde noch krank, Copilot möchte keinen Urlaub und so weiter.

Das ist keine Zukunftsmusik. Diese Technologie funktioniert bereits heute. Vor allem aber: Sie funktioniert erschreckend gut. Wir bei the native web haben Copilot in den vergangenen Wochen intensiv getestet und ausprobiert. Selbstverständlich liegt die KI manchmal daneben und produziert Code, der, gelinde gesagt, etwas merkwürdig ist. Mitunter versteht die KI auch noch nicht vollständig, was genau der Business Case ist. Doch das wird sich zukünftig kontinuierlich verbessern. In spätestens zwei Jahren wird diese KI-gestützte Arbeitsweise vermutlich der Standard sein. Dann stellt sich die Frage: Sind Sie darauf vorbereitet? Denn wie gesagt: Das ist kein Buzzword und kein Spielzeug. Das ist die neue Realität. Diese verändert gerade still und leise, wie Softwareentwicklung funktioniert – und zwar sehr viel radikaler, als die meisten ahnen.

Was bedeutet das nun konkret für Sie als Entwicklerin oder als Entwickler? Es ist ganz einfach: Wer lediglich Code schreibt, ohne wirklich zu verstehen, was sie oder er im Detail eigentlich macht, wer keine Weitsicht besitzt, keine Konsequenzen abschätzen kann und keine Architektur und keine Strategien entwickelt, wird durch die KI ersetzt. Und das geschieht nicht irgendwann, sondern demnächst. Der Grund liegt darin, dass es schneller, günstiger und präziser ist, die Aufgabe direkt der KI zu übergeben, statt sie erst Ihnen erklären zu müssen.

Anders gesagt: Wenn Sie sich nicht weiterentwickeln, werden Sie über kurz oder lang schlichtweg nicht mehr gebraucht. Denn das ist das eigentliche Problem: Möchten Sie wirklich in einer Rolle arbeiten, in der eine KI Entscheidungen für Sie trifft, die Sie später nicht mehr nachvollziehen können? Oder in der Sie noch nicht einmal einschätzen können, ob das generierte Ergebnis robust und langfristig tragfähig ist? Deshalb noch einmal: Wenn Sie nicht lernen, KI sinnvoll einzusetzen, wird Sie KI irgendwann ersetzen.

Übrigens: Genau darüber haben wir vor zwei Wochen bereits gesprochen. Damals ging es um einen Entwickler, der Code abgeliefert hat, den er selbst nicht verstanden hat, weil er lediglich eins zu eins übernommen hat, was ihm die KI vorgeschlagen hatte.

Für Sie als Unternehmerin oder Unternehmer bedeutet das gleichzeitig: Sie können Ihre Projekte künftig theoretisch wesentlich schneller und günstiger realisieren. Sie können Ihr Unternehmen deutlich effizienter aufstellen – und sie müssen das sogar, denn die Konkurrenz schläft nicht. Dazu ist jedoch erforderlich, dass Sie Ihre Teams richtig aufstellen, dass Sie die richtigen Personen fördern, dass Sie die Strukturen schaffen, die es ermöglichen, KI sinnvoll einzusetzen.

Genau an diesem Punkt liegt das Problem: Viele Unternehmen laufen derzeit KI nämlich lediglich als Buzzword hinterher. Weil es gerade schick klingt und weil es jeder irgendwie in seinem Pitchdeck haben möchte. Was wir immer wieder feststellen: Kaum jemand hat bislang wirklich verstanden, worauf es in diesem Zusammenhang ankommt. Kaum ein Unternehmen erkennt, welche Prozesse sie anpassen müssten, wen sie in ihren Teams weiterentwickeln sollten und welche Skills in Zukunft wirklich entscheidend sein werden. Genau das wird für viele Unternehmen über kurz oder lang extrem teuer werden.

Damit das alles für Sie möglichst eine gute Zukunft hat, benötigen Sie vor allem zwei Dinge.

Erstens: Entwicklerinnen und Entwickler müssen in der Tiefe verstehen, was sie tagtäglich machen. Unternehmen brauchen Menschen, die nicht nur Code schreiben, sondern die in der Lage sind, Architektur, Fachlichkeit und auch Konsequenzen zu durchdenken. Die dazu fähig sind, einen Vorschlag von Copilot zu überprüfen und nicht nur formal abzunicken, sondern tatsächlich zu entscheiden, ob das eine passende und adäquate Lösung ist – unter Berücksichtigung all der Aspekte, die langfristig in der Softwareentwicklung relevant sind.

Zweitens (und das ist vielleicht noch viel wichtiger) benötigen Sie die Fähigkeit, exakt mit natürlicher Sprache umzugehen: KI ist sprachgesteuert. Wer ungenau beschreibt, erhält ungenaue Ergebnisse. Wir sehen das immer wieder: Gute Prompts zu schreiben, ist eine Kunst. Viele Teams kommunizieren jedoch viel zu schwammig. Das fällt in Meetings oft nicht einmal auf, weil man dort – zumindest manchmal – merkt, dass man aneinander vorbeiredet oder gerade implizite Annahmen trifft. Wenn Sie allerdings nicht in der Lage sind, präzise zu formulieren, was Sie eigentlich haben möchten, und Sie das dann in entsprechend ungenauer Form einer KI vorgeben, dann erkennen Sie rasch: Das Ergebnis ist ebenso schwammig. Ganz gemäß dem alten Motto: Garbage in, Garbage out.

Das bedeutet: Gute Prompts erfordern präzises Denken, eine saubere Sprache und vor allem Klarheit im Kopf. Man könnte auch sagen: Prompts sind das neue Programmieren. Genau das, also gute Prompts zu formulieren, beherrschen die allermeisten jedoch nicht wirklich. Weil sie nie gelernt haben, möglichst exakt mit natürlicher Sprache umzugehen. Das betrifft den Fachbereich ebenso wie die Entwicklung. Doch das wird in Zukunft essenziell sein.


(rme)



Source link

Entwicklung & Code

Bitrig: iPhone-App entwickelt Apps direkt auf dem Gerät


Sie wird von einigen gefeiert, als hätte es so etwas vorher noch nie gegeben: Die App Bitrig für das iPhone sorgt aktuell für Schlagzeilen. Sie ermöglicht es, auf dem iPhone per Textprompt eine App entwickeln zu lassen. Dies erfolgt in Apples Programmiersprache Swift – also ohne den Kniff einer Web-App im Browserfenster, sondern in der Sprache für native Apps. Und wer knapp 23 Euro pro Monat zahlt, kann mit der App nicht nur intensiver arbeiten, sondern seine Erzeugnisse via TestFlight sogar App-Store-reif machen.

Bitrig selbst gibt dem Nutzer nach dem ersten Start Anregungen, was mit der App möglich ist: So kann mit wenigen Anweisungen ein Fahrtenbuch erstellt werden oder eine App, die ans regelmäßige Wassertrinken erinnert. Wer hierzu keine für ihn passende Lösung im App Store findet, macht sie sich einfach selbst, so der Gedanke. Die App ist dabei so schlicht und einfach gestaltet, dass jeder schnell zu ersten Erfolgserlebnissen kommen sollte.

Dennoch: Auf den ersten Blick verwundert die Aufregung im Netz. Vibe Coding, das Programmieren mithilfe einer Künstlichen Intelligenz und oftmals ohne (große) Kenntnisse des Bedieners, ist nichts Neues und gehört stattdessen sogar zu den häufigsten Anwendungsfällen von KI. Dies allerdings auf das iPhone zu bringen und den damit erzeugten Code direkt auf dem Gerät „zum Leben zu erwecken“, ist etwas, was es vorher noch nicht gab.

Apple sperrt sich nämlich weitgehend dagegen, iPhone und iPad für die App-Entwicklung freizugeben. Es galt vor ein paar Jahren schon als große Öffnung, als es mithilfe der Apple-eigenen Lern-App Swift Playgrounds plötzlich möglich war, die Entwicklung mit Swift nicht nur zu erlernen, sondern sogar eine komplette App damit zu entwickeln, die von dort aus bis in den App Store gebracht werden können. Dafür bemächtigte sich Apple allerdings Möglichkeiten, die Drittentwicklern aus Sicherheitsgründen nicht zur Verfügung stehen: das Kompilieren und Ausführen von Programmen auf dem Gerät.

Die Entwickler von Bitrig, die selbst früher bei Apple arbeiteten und an der Entstehung des SwiftUI-Frameworks beteiligt waren, bedienten sich deshalb eines Tricks: Sie interpretieren den erzeugten Code lediglich auf dem Gerät, was zulässig ist. Das Kompilieren für die TestFlight-Distribution, die im Pro-Abo enthalten ist, findet serverseitig statt. Praktischerweise, so legen sie es in einem Blogpost dar, gibt es in Swift bereits einen Parser namens SwiftSyntax, womit sie keinen verbotenen Weg in Form von Private APIs gehen mussten – das große Ausschlusskriterium Apples, wenn es darum geht, seine App im App Store zu veröffentlichen.

Doch mit dem Trick alleine ist es nicht getan. Apples umfangreichen Werkzeugkasten mussten sie dennoch auf ihre App übertragen. Aktuell stehen nicht alle Apple-Frameworks zur Verfügung. Mit MapKit und WidgetKit kann sich die KI zwar schon einer Menge aktueller Werkzeuge bedienen. Vieles in der Liste ist jedoch noch nicht abgehakt und muss von den Bitrig-Entwicklern noch vorbereitet und bereitgestellt werden.

Dieses Dienstleistungspaket lassen sie sich gut bezahlen. Im kostenlosen Modus sind täglich fünf Nachrichten an die KI inklusive. Nach 30 Prompts pro Monat ist erstmal Schluss. Wer den 23 Euro teuren Pro-Tarif abonniert, darf zwar auch nur fünf tägliche Nachrichten abschicken, aber immerhin bis zu 150 pro Monat und bekommt dann nochmal 100 obendrauf.

Erfahrene Vibecoder wundern sich trotzdem: Mit fünf Nachrichten pro Tag dürften aufwändige Ideen schwerlich zu realisieren sein. In ersten Tests zeigt sich die App zwar zielsicherer als ein allgemeines Sprachmodell wie ChatGPT oder Claude. Dies liegt daran, dass die Bitrig-Entwickler die Eingabe des Nutzers dahingehend erweitern, dass der Prompt besser ausformuliert wird und viele Details bedacht werden, an die der Laie nicht sofort denkt.

Dennoch dürfte es selbst für ambitionierte Hobbyentwickler die preisgünstigere Wahl sein, ein ähnlich teures Abo eines großen Sprachmodells abzuschließen und dies nahezu unbegrenzt verwenden zu können. Für den Anfang, besonders im kostenlosen Probiermodus, ist Bitrig aber zweifellos ein guter Ausgangspunkt für erste Gehversuche. Interessierte können so recht schnell für sich erkennen, was hinter einer spontanen Idee an Codearbeit dahintersteckt. Oder sie können ausprobieren, ob die Idee überhaupt funktioniert. Und der große Vorteil ist, dass all dies ohne Mac und ohne Xcode möglich ist, was für viele erstmal eine Hürde sein dürfte.


(mki)



Source link

Weiterlesen

Entwicklung & Code

Künstliche Neuronale Netze im Überblick 11: Implementierung eines Transformers


Neuronale Netze sind der Motor vieler Anwendungen in KI und GenAI. Diese Artikelserie gibt einen Einblick in die einzelnen Elemente. Der elfte und letzte Teil implementiert einen Transformer.


Michael Stal

Michael Stal

Prof. Dr. Michael Stal arbeitet seit 1991 bei Siemens Technology. Seine Forschungsschwerpunkte umfassen Softwarearchitekturen für große komplexe Systeme (Verteilte Systeme, Cloud Computing, IIoT), Eingebettte Systeme und Künstliche Intelligenz.

Er berät Geschäftsbereiche in Softwarearchitekturfragen und ist für die Architekturausbildung der Senior-Software-Architekten bei Siemens verantwortlich.

Der Transformer ist eine neuronale Architektur, die vollständig auf Aufmerksamkeitsmechanismen basiert und ohne Rekursion und Faltung auskommt, um Sequenzen parallel zu verarbeiten. Seine wichtigste Innovation ist die skalierte Skalarprodukt-Attention-Unterschicht, die gleichzeitig die Beziehungen zwischen allen Positionen in der Eingabe berechnet. Ein Transformer-Encoder stapelt mehrere Schichten von Multi-Head-Self-Attention- und positionsbezogenen Feed-Forward-Netzwerken, die jeweils in Restverbindungen und Schichtnormalisierung eingebettet sind. Der Decoder fügt maskierte Selbstaufmerksamkeit und Encoder-Decoder-Aufmerksamkeit hinzu, um eine autoregressive Generierung zu ermöglichen.

Wir beginnen mit der Formalisierung der skalierten Skalarprodukt-Aufmerksamkeit. Bei gegebenen Abfrage-, Schlüssel- und Wertematrizen Q, K und V mit den Formen (batch_size, num_heads, seq_len, d_k) berechnen wir Rohwerte, indem wir das Punktprodukt von Q mit der Transponierten von K bilden. Anschließend skalieren wir diese Werte mit √d_k, um zu verhindern, dass Extremwerte zu verschwindenden Gradienten führen, wenden Softmax an, um Aufmerksamkeitsgewichte zu erhalten, und multiplizieren mit V, um die beachtete Ausgabe zu erhalten:

Attention(Q, K, V) = softmax( (Q · Kᵀ) / √d_k ) · V

In PyTorch lässt sich die wie folgt umsetzen:


import torch
import torch.nn.functional as F

def scaled_dot_product_attention(Q, K, V, mask=None):
    """
    Berechnet die skalierte Skalarprodukt-Aufmerksamkeit.
    
    Q, K, V haben die Form (batch_size, num_heads, seq_len, d_k).
    Mask, falls angegeben, wird zu den Bewertungen hinzugefügt, um die Aufmerksamkeit auf bestimmte Positionen zu verhindern.
    """
    d_k = Q.size(-1)
    # Berechne die rohen Aufmerksamkeitswerte.
    scores = torch.matmul(Q, K.transpose(-2, -1)) / torch.sqrt(torch.tensor(d_k, dtype=torch.float32))
    # Wende die Maske an (z. B. um zu verhindern, dass nachfolgende Token im Decoder Beachtung finden).
    if mask is not None:
        scores = scores + mask
    # Normalisieren, um Aufmerksamkeitsgewichte zu erhalten.
    attn_weights = F.softmax(scores, dim=-1)
    # Berechne die gewichtete Summe der Werte.
    output = torch.matmul(attn_weights, V)
    return output, attn_weights


In dieser Funktion extrahieren wir die Dimension d_k aus Q, berechnen die Skalarprodukte, skalieren sie und fügen optional vor dem Softmax eine Maske hinzu. Die Maske enthält große negative Werte (−∞) an unzulässigen Positionen, sodass diese Positionen nach dem Softmax das Gewicht Null erhalten.

Multi-Head-Attention erweitert diesen Ansatz, indem es dem Modell ermöglicht, Informationen aus mehreren Darstellungsunterräumen gemeinsam zu berücksichtigen. Zunächst projizieren wir den Eingabetensor X der Form (batch_size, seq_len, d_model) mithilfe gelernter linearer Schichten auf Abfragen, Schlüssel und Werte. Anschließend teilen wir jede dieser Projektionen entlang der Merkmalsdimension in num_heads separate Köpfe auf, wenden die Skalarprodukt-Aufmerksamkeit parallel auf jeden Kopf an, verknüpfen die Ergebnisse und projizieren sie zurück auf das ursprüngliche d_model:


import torch
import torch.nn as nn

class MultiHeadAttention(nn.Module):
    def __init__(self, d_model, num_heads):
        super(MultiHeadAttention, self).__init__()
        assert d_model % num_heads == 0, "d_model muss durch num_heads teilbar sein"
        self.num_heads = num_heads
        self.d_k = d_model // num_heads
        # Lineare Projektionen für Abfragen, Schlüssel, Werte und die endgültige Ausgabe.
        self.W_q = nn.Linear(d_model, d_model)
        self.W_k = nn.Linear(d_model, d_model)
        self.W_v = nn.Linear(d_model, d_model)
        self.W_o = nn.Linear(d_model, d_model)

    def forward(self, X, mask=None):
        batch_size, seq_len, _ = X.size()
        # Projektionen der Eingaben auf Q, K, V.
        Q = self.W_q(X)
        K = self.W_k(X)
        V = self.W_v(X)
        # Umformen und transponieren, um Köpfe zu trennen.
        Q = Q.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)
        K = K.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)
        V = V.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)
        # Skalarprodukt-Aufmerksamkeit anwenden.
        attn_output, _ = scaled_dot_product_attention(Q, K, V, mask)
        # Köpfe verknüpfen und zurück auf d_model projizieren.
        concat = attn_output.transpose(1, 2).contiguous().view(batch_size, seq_len, -1)
        output = self.W_o(concat)
        return output


Da der Transformer keine integrierte Vorstellung von Reihenfolge hat, werden den Token-Einbettungen Positionscodierungen hinzugefügt, um dem Modell Informationen über die Position jedes Elements in der Sequenz zu liefern. Der ursprüngliche Transformer verwendet sinusförmige Codierungen, die wie folgt definiert sind:

P[pos, 2i ] = sin( pos / (10000^(2i/d_model)) )

P[pos, 2i+1 ] = cos( pos / (10000^(2i/d_model)) )

für pos in [0, L−1] und i in [0, d_model/2−1].

Wir implementieren dies wie folgt:


import torch
import math

def get_sinusoidal_positional_encoding(L, d_model):
    # Erstelle einen Tensor der Form (L, d_model).
    P = torch.zeros(L, d_model)
    position = torch.arange(0, L).unsqueeze(1).float()
    div_term = torch.exp(torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model))
    # Sinus auf gerade Indizes anwenden
    P[:, 0::2] = torch.sin(position * div_term)
    # Cosinus auf ungerade Indizes anwenden.
    P[:, 1::2] = torch.cos(position * div_term)
    return P


Jede Encoder-Schicht besteht aus einer Selbstaufmerksamkeits-Unterschicht, gefolgt von einem positionsbezogenen Feedforward-Netzwerk. Beide Unterschichten sind in Restverbindungen eingeschlossen. Darauf folgen eine Schichtnormalisierung und ein Dropout. Das Feedforward-Netzwerk hat die Form:

FFN(x) = ReLU(x·W₁ + b₁)·W₂ + b₂

und wird unabhängig auf jede Position angewendet. Wir erstellen eine Encoder-Schicht in PyTorch:


class TransformerEncoderLayer(nn.Module):
    def __init__(self, d_model, num_heads, d_ff, dropout=0.1):
        super(TransformerEncoderLayer, self).__init__()
        self.self_attn = MultiHeadAttention(d_model, num_heads)
        self.ffn = nn.Sequential(
            nn.Linear(d_model, d_ff),
            nn.ReLU(),
            nn.Linear(d_ff, d_model),
        )
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.dropout1 = nn.Dropout(dropout)
        self.dropout2 = nn.Dropout(dropout)

    def forward(self, x, mask=None):
        # Selbstaufmerksamkeit mit Restverbindung und Normalisierung.
        attn_out = self.self_attn(x, mask)
        x = x + self.dropout1(attn_out)
        x = self.norm1(x)
        # Feed-Forward mit Restverbindung und Normalisierung.
        ffn_out = self.ffn(x)
        x = x + self.dropout2(ffn_out)
        x = self.norm2(x)
        return x


Um den vollständigen Encoder zu erstellen, stapeln wir N solcher Schichten und wenden Positionscodierungen auf die Eingabe an:


class TransformerEncoder(nn.Module):
    def __init__(self, num_layers, d_model, num_heads, d_ff, dropout):
        super(TransformerEncoder, self).__init__()
        self.pos_encoder = get_sinusoidal_positional_encoding
        self.layers = nn.ModuleList([
            TransformerEncoderLayer(d_model, num_heads, d_ff, dropout)
            for _ in range(num_layers)
        ])
        self.norm = nn.LayerNorm(d_model)

    def forward(self, src, src_mask=None):
        # src: (batch_size, seq_len, d_model)
        seq_len = src.size(1)
        # Positionskodierung hinzufügen.
        pos_enc = self.pos_encoder(seq_len, src.size(2)).to(src.device)
        x = src + pos_enc.unsqueeze(0)
        # Durchlaufen jeder Encoder-Schicht.
        for layer in self.layers:
            x = layer(x, src_mask)
        return self.norm(x)


Die Implementierung eines Decoders folgt dem gleichen Muster, umfasst jedoch eine maskierte Selbstaufmerksamkeits-Unterschicht, um die Berücksichtigung nachfolgender Positionen zu verhindern, sowie eine Encoder-Decoder-Aufmerksamkeits-Unterschicht, die die Ausgabe des Encoders berücksichtigt. Eine abschließende lineare Softmax-Schicht ordnet die Decoder-Ausgabe den Wahrscheinlichkeiten im Zielvokabular zu.

Durch die Codierung jeder Komponente – von der Skalarprodukt-Aufmerksamkeit über Multi-Head-Aufmerksamkeit, Positionskodierung, Feed-Forward-Netzwerke bis hin zu Encoder-Schichten – erhalten Sie Einblicke in den Informationsfluss durch den Transformer. Auf dieser Grundlage lässt sich das Modell leicht an Aufgaben wie maschinelle Übersetzung, Textzusammenfassung oder sogar Bildgenerierung anpassen oder erweitern.

Um Sequenz-zu-Sequenz-Aufgaben wie maschinelle Übersetzung oder Zusammenfassung durchzuführen, müssen wir den Encoder zu einem gepaarten Decoder erweitern, der jeweils ein Token generiert und dabei die Ausgabe des Encoders berücksichtigt. Ein vollständiger Transformer umfasst somit Token-Einbettungen, Positionskodierungen, einen Stapel von Encoder-Schichten, einen Stapel von Decoder-Schichten und eine abschließende lineare Projektion in das Zielvokabular.

Nachfolgend finden Sie eine schrittweise Implementierung in PyTorch, wobei zu jeder Zeile eine Erläuterung erfolgt.


import math
import torch
import torch.nn as nn
import torch.nn.functional as F

def scaled_dot_product_attention(Q, K, V, mask=None):
    """
    Berechnet die skalierte Skalarprodukt-Aufmerksamkeit.
    
    Q, K, V sind Form (batch_size, num_heads, seq_len, d_k).
    Mask, falls angegeben, enthält -inf unzulässige Positionen.
    """
    d_k = Q.size(-1)
    
    # Berechne die rohen Aufmerksamkeitswerte durch Matrixmultiplikation der Abfragen mit den Schlüsseln.
    scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k)
    # Wenn eine Maske angegeben ist, füge sie hinzu (Positionen mit -inf bleiben nach Softmax Null).
    if mask is not None:
        scores = scores + mask
    # Normalisiere die Werte zu Wahrscheinlichkeiten.
    attn_weights = F.softmax(scores, dim=-1)
    # Multipliziere die Wahrscheinlichkeiten mit den Werten, um die beachteten Ausgaben zu erhalten.
    output = torch.matmul(attn_weights, V)
    return output, attn_weights

class MultiHeadAttention(nn.Module):
    def __init__(self, d_model, num_heads):
        super(MultiHeadAttention, self).__init__()
        # Sicherstellen, dass d_model gleichmäßig durch die Anzahl der Köpfe teilbar ist.
        assert d_model % num_heads == 0
        self.num_heads = num_heads
        self.d_k = d_model // num_heads
        # Lineare Projektionen für Abfragen, Schlüssel, Werte.
        self.W_q = nn.Linear(d_model, d_model)
        self.W_k = nn.Linear(d_model, d_model)
        self.W_v = nn.Linear(d_model, d_model)
        # Endgültige lineare Projektion nach Verkettung aller Köpfe.
        self.W_o = nn.Linear(d_model, d_model)

    def forward(self, query, key, value, mask=None):
        batch_size = query.size(0)
        # Projektion der Eingabetensoren in Q, K, V.
        Q = self.W_q(query)
        K = self.W_k(key)
        V = self.W_v(value)
        # In (batch, heads, seq_len, d_k) umformen und transponieren.
        Q = Q.view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
        K = K.view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
        V = V.view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
        # Skalierte Skalarprodukt-Aufmerksamkeit pro Kopf anwenden.
        attn_output, _ = scaled_dot_product_attention(Q, K, V, mask)
        # Köpfe verknüpfen: zurück transponieren und Kopfdimension zusammenführen.
        concat = attn_output.transpose(1, 2).contiguous().view(batch_size, -1, self.num_heads * self.d_k)
        # Endgültige lineare Projektion.
        output = self.W_o(concat)
        return output

class PositionalEncoding(nn.Module):
    def __init__(self, d_model, max_len=5000):
        super(PositionalEncoding, self).__init__()
        # Erstelle einmal sinusförmige Positionskodierungen.
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len).unsqueeze(1).float()
        
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model))
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        
        # Batch-Dimension hinzufügen und als Puffer registrieren, damit sie sich mit dem Modell mitbewegt.
        self.register_buffer('pe', pe.unsqueeze(0))

    def forward(self, x):
        # x hat die Form (batch_size, seq_len, d_model).
        # Die Positionskodierungen bis zur Eingabelänge hinzufügen.
        x = x + self.pe[:, :x.size(1)]
        return x

class TransformerEncoderLayer(nn.Module):
    def __init__(self, d_model, num_heads, d_ff, dropout):
        super(TransformerEncoderLayer, self).__init__()
        # Selbstaufmerksamkeits-Unterschicht.
        self.self_attn = MultiHeadAttention(d_model, num_heads)
        # Positionsbezogenes Feedforward-Netzwerk.
        self.ffn = nn.Sequential(
            nn.Linear(d_model, d_ff),
            nn.ReLU(),
            nn.Linear(d_ff, d_model),
        )
        # Layer-Normalisierungsmodule.
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        # Dropout zur Regularisierung.
        self.dropout1 = nn.Dropout(dropout)
        self.dropout2 = nn.Dropout(dropout)

    def forward(self, x, src_mask=None):
        # Selbstaufmerksamkeit anwenden, dann addieren und normieren.
        attn_out = self.self_attn(x, x, x, src_mask)
        x = self.norm1(x + self.dropout1(attn_out))
        # Feedforward-Netzwerk anwenden, dann addieren und normieren.
        ffn_out = self.ffn(x)
        x = self.norm2(x + self.dropout2(ffn_out))
        return x

class TransformerDecoderLayer(nn.Module):
    def __init__(self, d_model, num_heads, d_ff, dropout):
        super(TransformerDecoderLayer, self).__init__()
        # Maskierte Selbstaufmerksamkeit für Zielsequenz.
        self.self_attn = MultiHeadAttention(d_model, num_heads)
        # Encoder-Decoder-Aufmerksamkeit für die Quelle.
        self.src_attn = MultiHeadAttention(d_model, num_heads)
        # Feed-Forward-Netzwerk.
        self.ffn = nn.Sequential(
            nn.Linear(d_model, d_ff),
            nn.ReLU(),
            nn.Linear(d_ff, d_model),
        )
        
        # Layer-Normen und Dropouts für jede Unterschicht.
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.norm3 = nn.LayerNorm(d_model)
        
        self.dropout1 = nn.Dropout(dropout)
        self.dropout2 = nn.Dropout(dropout)
        self.dropout3 = nn.Dropout(dropout)

    def forward(self, x, memory, src_mask=None, tgt_mask=None):
        # Maskierte Selbstaufmerksamkeit auf dem Decodereingang.
        self_attn_out = self.self_attn(x, x, x, tgt_mask)
        x = self.norm1(x + self.dropout1(self_attn_out))
        # Encoder-Decoder-Aufmerksamkeit über Encoder-Ausgaben.
        src_attn_out = self.src_attn(x, memory, memory, src_mask)
        x = self.norm2(x + self.dropout2(src_attn_out))
        # Feed-forward und add & norm.
        ffn_out = self.ffn(x)
        x = self.norm3(x + self.dropout3(ffn_out))
        return x

class Transformer(nn.Module):
    def __init__(self,
                 src_vocab_size,
                 tgt_vocab_size,
                 d_model=512,
                 num_heads=8,
                 d_ff=2048,
                 num_encoder_layers=6,
                 num_decoder_layers=6,
                 dropout=0.1):
        super(Transformer, self).__init__()
        # Token-Einbettung für Quelle und Ziel.
        self.src_embed = nn.Sequential(
            nn.Embedding(src_vocab_size, d_model),
            PositionalEncoding(d_model)
        )
        
        self.tgt_embed = nn.Sequential(
            nn.Embedding(tgt_vocab_size, d_model),
            PositionalEncoding(d_model)
        )
        # Gestapelte Encoder- und Decoder-Schichten.
        self.encoder_layers = nn.ModuleList([
            TransformerEncoderLayer(d_model, num_heads, d_ff, dropout)
            for _ in range(num_encoder_layers)
        ])
        self.decoder_layers = nn.ModuleList([
            TransformerDecoderLayer(d_model, num_heads, d_ff, dropout)
            for _ in range(num_decoder_layers)
        ])
        # Endgültige lineare Projektion auf die Vokabulargröße.
        self.generator = nn.Linear(d_model, tgt_vocab_size)
        self.d_model = d_model

    def encode(self, src, src_mask=None):
        # Positionskodierung einbetten und hinzufügen.
        x = self.src_embed(src) * math.sqrt(self.d_model)
        # Durch jede Encoder-Schicht hindurchlaufen.
        for layer in self.encoder_layers:
            x = layer(x, src_mask)
        return x

    def decode(self, tgt, memory, src_mask=None, tgt_mask=None):
        # Ziel einbetten und Positionskodierung hinzufügen.
        x = self.tgt_embed(tgt) * math.sqrt(self.d_model)
        # Durchlaufen jeder Decoderschicht.
        for layer in self.decoder_layers:
            x = layer(x, memory, src_mask, tgt_mask)
        return x

    def forward(self, src, tgt, src_mask=None, tgt_mask=None):
        # Encoder-Ausgabe berechnen.
        memory = self.encode(src, src_mask)
        # Decoder-Ausgabe unter Berücksichtigung des Encoder-Speichers berechnen.
        output = self.decode(tgt, memory, src_mask, tgt_mask)
        # Auf Vokabular-Logits projizieren.
        return self.generator(output)

def generate_square_subsequent_mask(sz):
    """
    Erstellt eine Maske für kausale Aufmerksamkeit, sodass Position i nur
    auf Positionen ≤ i achten kann. Masken-Einträge sind 0, wo erlaubt, und
    -inf, wo nicht erlaubt.
    """
    mask = torch.triu(torch.full((sz, sz), float('-inf')), diagonal=1)
    return mask

# Anwendungsbeispiel mit Dummy-Daten:
# Vokabulargrößen und Sequenzlängen definieren.
src_vocab_size, tgt_vocab_size = 10000, 10000
batch_size, src_len, tgt_len = 2, 20, 22

# Transformer instanziieren.
model = Transformer(src_vocab_size, tgt_vocab_size)

# Beispiel für Quell- und Zieltoken-Indizes.
src = torch.randint(0, src_vocab_size, (batch_size, src_len))
tgt = torch.randint(0, tgt_vocab_size, (batch_size, tgt_len))

# Keine Füllmaske für dieses Beispiel.
src_mask = None
# Kausale Maske für den Decoder.
tgt_mask = generate_square_subsequent_mask(tgt_len)

# Der Vorwärtsdurchlauf liefert Logits der Form (batch_size, tgt_len, tgt_vocab_size).
logits = model(src, tgt, src_mask, tgt_mask)


In dieser Implementierung wendet jede Encoder-Schicht Multi-Head-Selbstaufmerksamkeit und ein positionsbezogenes Feedforward-Netzwerk an, jeweils mit Restverbindungen und Schichtnormalisierung. Jede Decoder-Schicht fügt einen maskierten Selbstaufmerksamkeitsschritt hinzu, um das Einsehen zukünftiger Token zu verhindern, sowie eine zusätzliche Encoder-Decoder-Aufmerksamkeit, die es dem Decoder ermöglicht, sich auf relevante Teile der Quellsequenz zu konzentrieren. Mit Sinuskurven erstellte Positionscodierungen fügen dem Modell Reihenfolgeinformationen hinzu, und die letzte lineare Schicht projiziert die Decoder-Ausgaben auf rohe Token-Scores.

Auf dieser Grundlage können Sie das Modell anhand von gelabelten (gepaarten) Textdaten trainieren, indem Sie einen geeigneten Verlust definieren (beispielsweise die Kreuzentropie zwischen den vorhergesagten Logits und den tatsächlichen Token-Indizes) und einen der zuvor beschriebenen Optimierer verwenden.

Diese Blogserie hat einen Rundflug über das große Terrain der Künstlichen Neuronalen Netze (KNNs) geboten. Dabei haben wir noch gar nicht alle Anwendungen betrachtet, in denen KNNs eine Rolle spielen. Hier sei exemplarisch das Beispiel Reinforcement Learning erwähnt, etwa DQN (Deep-Q Learning).

Durch ihre Struktur erweisen sich KNNs als adäquates Mittel, um statistisches Pattern-Matching durchzuführen, also um bestimmte Muster in Eingangsdaten aufzuspüren. Diese Fähigkeit kommt speziell bei Transformer-Architekturen zum Tragen, die Prompts verarbeiten, um daraus Rückmeldungen zu liefern. Zusätzlich findet dort häufig Reinforcement Learning statt, um die Large Language Models zu bestimmten gewünschten Abläufen zu „überreden“, etwa zum sogenannten Reasoning.

Heutige KNNs sind an biologische neuronale Netze (BNNs) angelehnt, sind diesen gegenüber aber gewaltig eingeschränkt. In Zukunft könnten Wissenschaftler versuchen, sich bei KNNs noch deutlicher ihren biologischen Vorbildern anzunähern. Insgesamt dürften Künstliche Neuronale Netze in der Zukunft Generative KI eine fundamentale und zentrale Rolle spielen.


(rme)



Source link

Weiterlesen

Entwicklung & Code

Software Testing: Karriere als Software-Tester


Richard Seidl spricht mit Christine Pinto über Karrierewege im Testing und wie Testerinnen und Tester ihren Wert sichtbar machen können. Sie räumen mit Stellenanzeigen-Mythen auf und schärfen das Selbstverständnis als Qualitätscoach statt Bug-Finder.

Christine Pinto zeigt, wie man gute Interviews dreht: mit guten Fragen, Emotionen, konkreten Beispielen und Risikodenken, vom Technik- bis zum CEO-Gespräch. Das Duo spricht über Netzwerke und Empfehlungen, über Gehaltsverhandlungen und Haltung. Und sie streifen Themen wie Accessibility und die Verantwortung des ganzen Teams.

Bei diesem Podcast dreht sich alles um Softwarequalität: Ob Testautomatisierung, Qualität in agilen Projekten, Testdaten oder Testteams – Richard Seidl und seine Gäste schauen sich Dinge an, die mehr Qualität in die Softwareentwicklung bringen.

Die aktuelle Ausgabe ist auch auf Richard Seidls Blog verfügbar: „Karriere als Software-Tester – Christine Pinto“ und steht auf YouTube bereit.


(mdo)



Source link

Weiterlesen

Beliebt