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.
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.
Vollständiger Encoder-Decoder-Transformer
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.
… und noch mehr
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)
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)
Entwicklung & Code
KI-Desktop im Browser von Anthropic: Anwendungen gestalten mit Prompts
Das KI-Start-up Anthropic hat diese Woche nicht nur sein Modell Claude Sonnet 4.5 veröffentlicht, sondern auch ein experimentelles Feature vorgestellt: Imagine with Claude. Die virtuelle Desktop-Umgebung können Nutzerinnen und Nutzer per KI-Chat umgestalten und Anwendungen darauf allein per Texteingabe erzeugen – Software, die im Moment des Bedarfs entsteht, statt im Vorfeld programmiert zu werden.
Die Technologie-Vorschau läuft nur fünf Tage und steht ausschließlich zahlenden Abonnenten von Claude Max zur Verfügung. Für alle anderen gibt es nur ein Video auf YouTube:
Anthropic selbst bezeichnet das Ganze als Machbarkeitsstudie. Dennoch zeigt sich schon jetzt, wie KI-gestützte Softwareentwicklung künftig aussehen könnte.
Software aus dem Nichts
Nach Aktivierung öffnet sich eine Browser-basierte Desktop-Oberfläche mit Icons, Fenstern und Papierkorb. Jede Eingabe kann zur spontanen Erstellung einer Anwendung führen. Anders als bei klassischen Tools liegen Funktionen nicht fertig vor, sondern Claude generiert den nötigen Code bei jeder Interaktion. Die KI weiß, dass sie eine Webseite steuert, kennt aber zu Beginn keine Details über deren Inhalte.
Die Desktop-Oberfläche von Imagine with Claude läuft im Browserfenster. Unten findet sich das Chat-Feld.
Im unteren Bereich befindet sich das Eingabefeld. Auf den Befehl „Remove the yellow notes from the desktop“ zeigt Claude beim Arbeiten seine internen Überlegungen an, sucht mögliche Selektoren und löscht anschließend die Notizen.
The user wants to remove yellow notes from the desktop. I need to look for elements that represent yellow notes/sticky notes on the desktop and remove them.
However, I don’t have any information about the current state of the DOM or what elements exist on the desktop. The user is asking me to remove something, but I need to know what selector to use.
Since this is the first interaction and I don’t have context about what’s on the screen, I should ask for clarification or try common selectors for note elements. But the instruction says „Answer the user’s request using the relevant tool(s)“ – so I should try to remove elements that might be yellow notes.
Common selectors for notes might be: – Elements with class „note“ or „sticky-note“ or „yellow-note“ – Elements with id containing „note“ – Elements with specific background colors
But I don’t actually know what’s on the screen. The user said „Remove the yellow notes from the desktop“ – this implies there ARE yellow notes currently visible that they want removed.
Since I don’t have the current DOM state, I should try common selectors. Let me try: – .note (common class name) – .sticky-note – [class*=“note“]
Actually, looking at the system prompt again – it says the user is seeing HTML progressively and I’m reacting to their actions. This suggests there might already be content on the page that I created or that exists.
Since I don’t have visibility into what’s currently on screen, I’ll try to remove elements with common note-related selectors. If they don’t exist, the tool will simply not find anything to remove.
Let me try removing elements with class „note“ or similar. dom_remove dom_remove dom_remove Done Context: [█░░░░░░░░░░░] 15k/100k (15%)
Das klappt aber nicht immer so gut. Wiederholt man den Test mit einem frischen Desktop, scheitert die KI teils an dieser vergleichsweise einfachen Aufgabe.
Auch der verfügbare Kontext ist mit 100k Token begrenzt. Ist der Speicher voll, hilft nur ein Neustart unter claude.ai/imagine. Dort lässt sich etwa im nächsten Versuch mit „Open a terminal“ ein Terminalfenster öffnen, in dem Befehle wie ls
funktionieren. Dateien und Verzeichnisse sind nicht real, stattdessen generiert Imagine sie dynamisch – samt Darstellung im Finder. Dabei simuliert Claude das komplette Desktop-System. Wenn man im Terminalfenster eine Datei anlegt, ändert oder löscht, dann sieht man parallel im Finder-Fenster die Auswirkungen dieser Befehle.
Änderungen, die Anwender im Terminalfenster machen, erscheinen auch im Dateimanager.
Ein weiteres Beispiel: Auf die Eingabe „Erstelle eine Datenbank mit den Lottozahlen der letzten 10 Jahre“ startet Claude selbstständig eine Internetrecherche und sammelt die Daten, um sie in einer neuen Applikation anzuzeigen. Mit Prompt-Eingaben kann man diese Applikation um Feature-Requests wie „Zeige die am meisten gezogenen Zahlen“ erweitern. Wenn man auf Elemente der Applikation klickt, überlegt sich Claude, wie die Applikation auf diesen Klick reagiert und zeigt das dann an. Unterschiedliche Applikationen können innerhalb des gleichen Kontexts Daten austauschen.
Für die Lottozahlen-Datenbank startet Claude selbsttätig eine Internetrecherche.
Bugs entstehen und Anwender müssen sie mit neuen Prompts geradeziehen. Eine digitale Uhr konnte ich mit einem Prompt erzeugen. Eine analoge Uhr benötigte drei weitere Eingaben, bis sie funktionierte.
Möglich macht das Claude Sonnet 4.5 in Kombination mit einer Agenten-basierten Steuerung der UI. Im Hintergrund arbeitet ein Werkzeugkasten mit Befehlen wie WINDOW NEW
oder DOM REPLACE HTML
. Claude generiert und testet Code in einer Sandbox-ähnlichen Umgebung, um die gewünschte Funktionalität umzusetzen. Bei komplexeren Interaktionen entstehen dabei spürbare Verzögerungen.
Perspektiven und Diskussion
Die Einsatzmöglichkeiten reichen vom Rapid Prototyping über kleine Hilfs-Apps bis hin zur Vision eines KI-Betriebssystems.
Offen bleiben allerdings zentrale Fragen: Stabilität, Sicherheit und Verlässlichkeit einer KI, die direkt Code ausführt. Laut Anthropic ist Claude 4.5 stärker auf Sicherheit (Alignment) trainiert und mit Schutzmechanismen gegen Missbrauch ausgestattet.
Während Bildgeneratoren wie Midjourney oder DALL·E 3 Medieninhalte erzeugen, zielt Anthropic klar auf Softwareerstellung. Damit unterscheidet sich der Ansatz auch von OpenAIs jüngsten Erweiterungen, die stärker in Richtung allgemeiner Assistenten gehen.
Ein ähnliches Konzept liegt dem Firebase Studio von Google zugrunde. Ebenfalls im Browser gestalten die Nutzer Anwendungen per Sprachbefehl, während sie die Ergebnisse live im selben Fenster sehen. Allerdings dient das Tool der Entwicklung von Webanwendungen und es wirkt mehr wie ein WYSWYG-Editor als ein Desktop.
Fazit
Virtuelle Desktops im Browser sind keine neue Idee: Schon Ende der 1990er experimentierte Netscape-Gründer Marc Andreessen damals in Java mit vergleichbaren Konzepten. Wirklich durchgesetzt haben sie sich jedoch nie. Imagine with Claude geht nun einen Schritt weiter und stellt das Prinzip klassischer Softwareverteilung infrage: Programme werden nicht installiert, sondern ad hoc durch Spracheingaben erzeugt.
Auch wenn es sich nur um eine kurze Vorschau handelt, liefert das Experiment einen faszinierenden Ausblick – und dürfte die Debatte um KI-generierte Software weiter anheizen.
(who)
Entwicklung & Code
Die Produktwerker: Wie viel Scrum-Wissen brauchst du als Product Owner?
Ist Scrum-Wissen als Product Owner (PO) eigentlich notwendig, um die eigene Rolle gut auszufüllen? Tim Klein und Oliver Winter erleben in ihrer Arbeit, dass hier oft Unsicherheit herrscht. Manche POs fühlen sich unter Druck, jedes Detail auswendig kennen zu müssen, andere verlassen sich stark auf Scrum Master oder Teams und verlieren dabei das Verständnis für die Grundlagen. Und gilt das auch, wenn ich mit meinem Team gar kein Scrum mache?
Klar ist, dass Product Owner die Verantwortung für den Produkterfolg tragen. Dafür braucht es nicht nur ein Gespür für Markt, Kunden und Strategie, sondern auch ein solides Fundament im Scrum-Framework. Wer nicht weiß, wie Artefakte, Events und Verantwortlichkeiten ineinandergreifen, kann kaum sicherstellen, dass das Team wirkungsvoll zusammenarbeitet. Gleichzeitig reicht reines Regelwissen nicht aus. Ein Product Owner muss die Prinzipien verstehen, um sie im Alltag an den richtigen Stellen anzuwenden.
Verständnis für die Funktionsweise von Scrum
Scrum-Wissen ist dabei kein Selbstzweck. Es geht nicht darum, im Scrum Guide jede Passage zitieren zu können, sondern um ein Verständnis dafür, wie das Framework Teams unterstützt. Eine Product Ownerin, die versteht, warum das Sprintziel Orientierung gibt, warum ein Backlog nicht nur eine Liste, sondern ein Instrument der Fokussierung ist, und wie das Inkrement Vertrauen bei Stakeholdern schafft, wird wirkungsvoller arbeiten können.
Oliver Winter und Tim Klein betonen, dass Product Owner nicht alles alleine schultern müssen. Scrum ist ein Team-Framework, und die Verantwortung verteilt sich bewusst. Doch ohne eigenes Scrum-Wissen droht die Gefahr, die Rolle zu stark auf Stakeholder-Management oder Roadmaps einzuengen. Wer dagegen die Regeln und Prinzipien kennt, kann selbstbewusst auftreten, die Zusammenarbeit mit dem Scrum Master gestalten und dem Team eine klare Richtung geben.
An Erfahrung wachsen
Ein weiterer Aspekt ist die Weiterentwicklung. Scrum-Wissen ist nichts Statisches, das man einmal lernt und dann abhakt. Mit jeder Retrospektive, jeder Review und jedem Refinement wächst die Erfahrung, wie die Prinzipien in der eigenen Organisation wirken. Gerade in komplexen Umfeldern braucht es die Bereitschaft, immer wieder Neues auszuprobieren und das eigene Verständnis von Scrum zu hinterfragen.
Am Ende geht es nicht darum, als Product Owner ein wandelndes Scrum-Lexikon zu sein. Entscheidend ist, die Grundlagen so zu verinnerlichen, dass sie Orientierung geben, ohne dogmatisch zu wirken. Wer Scrum-Wissen klug einsetzt, schafft Klarheit im Team, fördert Verantwortung und sorgt dafür, dass das Framework seinen eigentlichen Zweck erfüllt: den Raum für wirksame Produktentwicklung.
(Bild: deagreez/123rf.com)
So geht Produktmanagement: Auf der Online-Konferenz Product Owner Day von dpunkt.verlag und iX am 13. November 2025 können Product Owner, Produktmanagerinnen und Service Request Manager ihren Methodenkoffer erweitern, sich vernetzen und von den Good Practices anderer Unternehmen inspirieren lassen.
Weiterbildungsmöglichkeiten
In der Diskussion wird auf den kostenfreien Scrum Foundations Online-Kurs der Agile Academy verwiesen. Ähnliches gibt es auch als Agile Fundamentals Online-Kurs. Die Produktwerker können hier aber auch explizit den ausführlichen Product Owner Online-Kurs mit satten zwanzig Stunden Videomaterial und Übungen empfehlen.
Wie im Podcast erwähnt, bieten die Produktwerker selbst Zertifizierungstrainings (vor Ort in Köln) an. Oliver Winter ist hierbei der Trainer. Neben dem Training „Product Owner Level 1“ gibt es auch das spannende „Product Owner Level 2“ Training als wertvolle Weiterführung für alle, die schon PSPO I (von Scrum.org) oder CSPO (von der Scrum Alliance) und weitere Erfahrung als Product Owner. Beide Trainings bietet Oliver b.a.W. jeweils einmal im Quartal an – und wie erwähnt gibt es im Anschluss die Zertifizierung der Agile Academy.
Warum wir nun auch zertifizierte Trainings anbieten, erklärt sich durch diese recht aktuelle Podcast-Folge Anwendungsnahe Weiterbildung – für mehr Erfolg als Product Owner und diesen LinkedIn-Beitrag von Oliver.
Verweise auf ältere Folgen
Die aktuelle Ausgabe des Podcasts steht auch im Blog der Produktwerker bereit: „Wie viel Scrum-Wissen brauchst du als Product Owner?„.
(mdo)
-
UX/UI & Webdesignvor 1 Monat
Der ultimative Guide für eine unvergessliche Customer Experience
-
UX/UI & Webdesignvor 1 Monat
Adobe Firefly Boards › PAGE online
-
Social Mediavor 1 Monat
Relatable, relevant, viral? Wer heute auf Social Media zum Vorbild wird – und warum das für Marken (k)eine gute Nachricht ist
-
Entwicklung & Codevor 1 Monat
Posit stellt Positron vor: Neue IDE für Data Science mit Python und R
-
Entwicklung & Codevor 4 Wochen
EventSourcingDB 1.1 bietet flexiblere Konsistenzsteuerung und signierte Events
-
UX/UI & Webdesignvor 2 Wochen
Fake It Untlil You Make It? Trifft diese Kampagne den Nerv der Zeit? › PAGE online
-
Digital Business & Startupsvor 3 Monaten
10.000 Euro Tickets? Kann man machen – aber nur mit diesem Trick
-
Apps & Mobile Entwicklungvor 2 Monaten
Firefox-Update 141.0: KI-gestützte Tab‑Gruppen und Einheitenumrechner kommen