Back to "Hoe Neurale Machine Vertaling werkt Eigenlijk: Een Ontwikkelaarsgids"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

AI AI-Article Deep Learning EasyNMT Machine Learning Neural Machine Translation

Hoe Neurale Machine Vertaling werkt Eigenlijk: Een Ontwikkelaarsgids

Sunday, 09 November 2025

Inleiding

Als je deze blog hebt gevolgd, weet je dat ik een beetje geobsedeerd ben door machine vertaling.Ik heb geschreven over, gebruik van EasyNMThet bouwen van vertaaldiensten voor achtergronden;, en zelfsEasyNMT verbeteren**Maar ik realiseerde me dat ik het nooit echt heb uitgelegd.

hoe

neurale machine vertaling werkt onder de motorkap.

OPMERKING: Dit is onderdeel van mijn experimenten met AI / een manier om $1000 Calude Code Web credits te besteden.

  • Ik heb dit een BUNCH van papieren gevoerd, mijn begrip, vragen die ik moest genereren van dit artikel.
  • Het is leuk en vult een gat dat ik nergens anders heb gezien.
  • Dus laten we dat repareren!
  • Dit bericht zal u van "vertaling is magie" naar "vertaling is slimme wiskunde" door het uitleggen van de belangrijkste concepten achter neurale machine vertaling in gewoon Engels (met veel diagrammen en een aantal C# code om het concreet te maken).
  • Aan het einde van deze post, zul je begrijpen:

Wat kunstmatige neurale netwerken zijn en hoe ze leren

Hoe woorden getallen worden (inbeddingen)

Wat "aandacht" betekent in AI (spoiler: het gaat niet over aandacht besteden)

graph LR
    A[Source Sentence] --> B[Convert to Numbers]
    B --> C[Neural Network Processing]
    C --> D[Convert to Words]
    D --> E[Translated Sentence]

Hoe werkt de encoder-decoder architectuur?

Waarom transformatoren de wereld overnamen

Maak je geen zorgen als je geen wiskunde persoon bent - ik zal dit zo praktisch en visueel mogelijk houden.

Laten we erin duiken!

Het kernidee: Vertaling als Number Crunching

  1. Hier is het eerste mind-bending concept: neurale machine vertaling behandelt vertaling als een puur wiskundig proces.
  2. Je voedt je in een zin, het systeem zet het om naar getallen, voert miljoenen wiskundige operaties uit, en verschijnt een vertaling.
  3. Maar wacht - hoe zet je woorden om naar getallen?
  4. En hoe begrijpt wiskunde de taal?

Laten we beginnen met de bouwstenen.

graph LR
    I1[Input 1] -->|weight: 0.8| N[Neuron]
    I2[Input 2] -->|weight: -0.3| N
    I3[Input 3] -->|weight: 0.5| N
    N --> O[Output]

    style N stroke-width:4px

Artificiële Neurale Netwerken: Stichting

public class Neuron
{
    private double[] weights;
    private double bias;

    public double Activate(double[] inputs)
    {
        // Step 1: Multiply each input by its weight and sum them
        double sum = bias;
        for (int i = 0; i < inputs.Length; i++)
        {
            sum += inputs[i] * weights[i];
        }

        // Step 2: Apply activation function (tanh keeps values between -1 and 1)
        return Math.Tanh(sum);
    }
}

Voordat we neurale machine vertaling kunnen begrijpen, moeten we kunstmatige neurale netwerken begrijpen.Math.TanhOndanks de mooie naam, zijn ze eigenlijk vrij eenvoudig in hun kern.

Wat is een Artificial Neuron?

Een kunstmatig neuron is een wiskundige functie die:

graph LR
    subgraph Input Layer
        I1[Input 1]
        I2[Input 2]
        I3[Input 3]
    end

    subgraph Hidden Layer 1
        H1[Neuron 1]
        H2[Neuron 2]
        H3[Neuron 3]
        H4[Neuron 4]
    end

    subgraph Hidden Layer 2
        H5[Neuron 5]
        H6[Neuron 6]
        H7[Neuron 7]
    end

    subgraph Output Layer
        O1[Output 1]
        O2[Output 2]
    end

    I1 --> H1 & H2 & H3 & H4
    I2 --> H1 & H2 & H3 & H4
    I3 --> H1 & H2 & H3 & H4

    H1 --> H5 & H6 & H7
    H2 --> H5 & H6 & H7
    H3 --> H5 & H6 & H7
    H4 --> H5 & H6 & H7

    H5 --> O1 & O2
    H6 --> O1 & O2
    H7 --> O1 & O2

Neemt meerdere ingangen

  • Vermenigvuldigt elke invoer met een gewicht (hoe belangrijk is deze invoer?)
  • Voegt ze allemaal toe
  • Past een "activeringsfunctie" toe om een uitvoer te produceren

Hier is een visuele voorstelling:

Laten we dit beton maken met wat C# code:

graph TD
    A[Start with Random Weights] --> B[Feed in Training Example]
    B --> C[Network Makes Prediction]
    C --> D{Is Prediction Correct?}
    D -->|No| E[Calculate Error]
    E --> F[Adjust Weights Slightly]
    F --> B
    D -->|Yes| G[Try Next Example]
    G --> B

    style E stroke-width:2px
    style F stroke-width:2px

De activeringsfunctie (in dit geval**) is wat neurale netwerken interessant maakt.**Het introduceert niet-lineairheid, waardoor het netwerk complexe patronen kan leren.

Zonder dat, hoeveel neuronen je ook bij elkaar hebt gestapeld, heb je gewoon een mooie lineaire functie.

public class SimpleNeuralNetwork
{
    private double learningRate = 0.01;
    private Neuron[] neurons;

    public void Train(TrainingExample[] examples, int epochs)
    {
        for (int epoch = 0; epoch < epochs; epoch++)
        {
            foreach (var example in examples)
            {
                // Forward pass: make a prediction
                double prediction = Predict(example.Input);

                // Calculate error
                double error = example.ExpectedOutput - prediction;

                // Backward pass: adjust weights
                // (simplified - real networks use backpropagation)
                AdjustWeights(error * learningRate);
            }
        }
    }
}

Van Neuronen naar Netwerken**De magie gebeurt als je duizenden of miljoenen van deze neuronen met elkaar verbindt in lagen:**Elke laag "verfijnt" de weergave van de input, het extraheren van steeds abstracter patronen.

In de vertaling:

Vroege lagen kunnen detecteren "dit is een werkwoord" of "dit woord is verleden tijd"

Middelste lagen kunnen detecteren "dit is een vraag" of "dit gaat over het weer"

Latere lagen combineren deze tot "vertaal dit als een beleefde vraag over het weer van morgen"

// Don't do this!
var wordToNumber = new Dictionary<string, int>
{
    {"cat", 1},
    {"dog", 2},
    {"king", 3},
    {"queen", 4},
    {"man", 5},
    {"woman", 6}
};

Hoe netwerken leren: Het trainingsproces

Hier is het slimme deel: we stellen niet al die gewichten handmatig in.

Het netwerk leert ze van voorbeelden!Dit proces heethellingsdaling

public class WordEmbedding
{
    // Each word is represented by a vector of floats
    private Dictionary<string, float[]> embeddings;

    public float[] GetEmbedding(string word)
    {
        return embeddings[word]; // e.g., [0.2, -0.5, 0.8, 0.1, ...]
    }

    // Calculate similarity between two words
    public double Similarity(string word1, string word2)
    {
        var vec1 = GetEmbedding(word1);
        var vec2 = GetEmbedding(word2);

        // Cosine similarity: how "aligned" are the vectors?
        return CosineSimilarity(vec1, vec2);
    }

    private double CosineSimilarity(float[] a, float[] b)
    {
        double dot = 0, magA = 0, magB = 0;
        for (int i = 0; i < a.Length; i++)
        {
            dot += a[i] * b[i];
            magA += a[i] * a[i];
            magB += b[i] * b[i];
        }
        return dot / (Math.Sqrt(magA) * Math.Sqrt(magB));
    }
}
graph TD
    subgraph "2D Projection of 300D Space"
        King[King<br/>0.8, 0.6]
        Queen[Queen<br/>0.75, 0.55]
        Man[Man<br/>0.3, 0.2]
        Woman[Woman<br/>0.25, 0.15]
        Dog[Dog<br/>-0.6, 0.4]
        Cat[Cat<br/>-0.65, 0.38]
    end

    King -.similar.-> Queen
    Man -.similar.-> Woman
    Dog -.similar.-> Cat
    King -.same vector.-> Man
    Queen -.same vector.-> Woman

Voor elk verkeerd antwoord komt het netwerk erachter "welke gewichten het meest verantwoordelijk waren voor deze fout?" en tweaks ze een klein beetje.king - man + woman ≈ queen

public float[] AnalogicalReasoning(string a, string b, string c)
{
    // king - man + woman = ?
    var vecA = GetEmbedding(a); // king
    var vecB = GetEmbedding(b); // man
    var vecC = GetEmbedding(c); // woman

    var result = new float[vecA.Length];
    for (int i = 0; i < vecA.Length; i++)
    {
        result[i] = vecA[i] - vecB[i] + vecC[i];
    }

    // Find the word closest to this vector
    return FindClosestWord(result); // Should return "queen"
}

Na het zien van miljoenen voorbeelden, de gewichten zich vestigen in waarden die goed werken.

Hier is een vereenvoudigde versie in C#:

graph LR
    A["The cat sat on the mat"] --> B[Train Neural Network]
    B --> C["cat → [0.1, 0.5, -0.3, ...]"]
    B --> D["sat → [0.2, -0.1, 0.4, ...]"]
    B --> E["mat → [0.15, 0.45, -0.25, ...]"]

Echte neurale netwerken gebruiken een meer geavanceerde algoritme genaamd

backpropagatie

dat efficiënt berekent hoe gewichten in het hele netwerk aan te passen, maar het principe is hetzelfde: leren van fouten.

graph TD
    subgraph English
        E1[The]
        E2[cat]
        E3[sat]
        E4[on]
        E5[the]
        E6[mat]
    end

    subgraph French
        F1[Le]
        F2[chat]
        F3[s'est assis]
        F4[sur]
        F5[le]
        F6[tapis]
    end

    E2 -. focus 90% .-> F2
    E3 -. focus 70% .-> F3
    E4 -. focus 80% .-> F4
    E6 -. focus 85% .-> F6
    E1 -. focus 30% .-> F1

Woord inbeddingen: Taal veranderen in wiskundeNu hebben we onze eerste grote uitdaging: hoe voeden we woorden in een neuraal netwerk?

We moeten tekst omzetten in getallen.

Het probleem met One-Hot codering

public class AttentionMechanism
{
    // Calculate attention weights for each source word
    public double[] CalculateAttention(
        float[] currentTargetState,     // Where we are in translation
        float[][] sourceWordStates)     // All source words
    {
        int sourceLength = sourceWordStates.Length;
        double[] scores = new double[sourceLength];

        // Step 1: Calculate relevance scores
        for (int i = 0; i < sourceLength; i++)
        {
            scores[i] = DotProduct(currentTargetState, sourceWordStates[i]);
        }

        // Step 2: Convert to probabilities (softmax)
        return Softmax(scores);
    }

    public float[] ApplyAttention(
        double[] attentionWeights,
        float[][] sourceWordStates)
    {
        // Create weighted average of source words
        int dim = sourceWordStates[0].Length;
        float[] result = new float[dim];

        for (int i = 0; i < sourceWordStates.Length; i++)
        {
            for (int j = 0; j < dim; j++)
            {
                result[j] += (float)(attentionWeights[i] * sourceWordStates[i][j]);
            }
        }

        return result;
    }

    private double[] Softmax(double[] scores)
    {
        double[] result = new double[scores.Length];
        double sum = 0;

        for (int i = 0; i < scores.Length; i++)
        {
            result[i] = Math.Exp(scores[i]);
            sum += result[i];
        }

        for (int i = 0; i < scores.Length; i++)
        {
            result[i] /= sum;
        }

        return result;
    }

    private double DotProduct(float[] a, float[] b)
    {
        double sum = 0;
        for (int i = 0; i < a.Length; i++)
        {
            sum += a[i] * b[i];
        }
        return sum;
    }
}

De naïeve benadering is "one-hot encoding" - geef elk woord een uniek getal:

  1. Maar dit heeft een enorm probleem: de getallen zijn willekeurig.
  2. Het netwerk kan niet zeggen dat "kat" en "hond" meer op elkaar lijken dan "kat" en "koningin."
  3. De nummers 1 en 2 betekenen niets.
  4. De oplossing: Woord inbeddingen

In plaats daarvan vertegenwoordigen we elk woord als een

vector

graph TD
    subgraph "English (Source)"
        E1[The]
        E2[agreement]
        E3[on]
        E4[the]
        E5[European]
        E6[Economic]
        E7[Area]
    end

    subgraph "German (Target)"
        G1[Das]
        G2[Abkommen]
        G3[über]
        G4[den]
        G5[Europäischen]
    end

    E1 -->|0.8| G1
    E2 -->|0.9| G2
    E3 -->|0.6| G3
    E4 -->|0.3| G3
    E5 -->|0.85| G5
    E6 -->|0.7| G5

van getallen, meestal 300-1000 dimensies.

Soortgelijke woorden krijgen vergelijkbare vectoren:

Dit is wat maakt inbedden magisch - ze vastleggen semantische relaties:**Het beroemde voorbeeld:**Hoe worden inbeddingen geleerd?

graph LR
    subgraph "Self-Attention for 'bank'"
        B[bank]
        R[river]
        W[water]
        F[fish]
    end

    B -.high attention.-> R
    B -.high attention.-> W
    B -.medium attention.-> F

Inbeddingen worden geleerd door een neuraal netwerk te trainen op een eenvoudige taak: "voorspel de omliggende woorden."

Het idee is dat woorden die in vergelijkbare contexten verschijnen, soortgelijke betekenissen moeten hebben.

Na training over miljarden zinnen, worden woorden die in vergelijkbare contexten verschijnen met soortgelijke inbeddingen. "Cat" en "hond" beide verschijnen in de buurt van "pet," "feed" en "schattig," zodat hun inbeddingen dicht bij elkaar eindigen.Attentie: de spelwisselaarHier is een kritisch inzicht: bij het vertalen van "De kat zat op de mat" naar het Frans, verschillende bron woorden belangrijk voor verschillende doel woorden:

graph LR
    subgraph Encoder
        E1[Word Embeddings] --> E2[Self-Attention Layer 1]
        E2 --> E3[Self-Attention Layer 2]
        E3 --> E4[Self-Attention Layer N]
        E4 --> E5[Contextual Representations]
    end

    subgraph Decoder
        D1[Previous Translations] --> D2[Self-Attention]
        D2 --> D3[Cross-Attention to Encoder]
        D3 --> D4[Feed Forward]
        D4 --> D5[Next Word Prediction]
    end

    E5 -.provides context.-> D3

Attentie.

is een mechanisme waarmee het netwerk "focus" op verschillende delen van de invoer bij het genereren van elk uitvoerwoord.

public class TransformerEncoder
{
    private WordEmbedding wordEmbedding;
    private SelfAttentionLayer[] layers;

    public float[][] Encode(string[] sourceWords)
    {
        // Step 1: Convert words to embeddings
        float[][] embeddings = sourceWords
            .Select(w => wordEmbedding.GetEmbedding(w))
            .ToArray();

        // Step 2: Add positional encoding (so network knows word order)
        float[][] withPositions = AddPositionalEncoding(embeddings);

        // Step 3: Apply multiple self-attention layers
        float[][] representations = withPositions;
        foreach (var layer in layers)
        {
            representations = layer.Forward(representations);
        }

        return representations; // Contextual representations for each word
    }

    private float[][] AddPositionalEncoding(float[][] embeddings)
    {
        // Add position-specific patterns so network knows word order
        // (transformers don't naturally understand sequence order)
        int sequenceLength = embeddings.Length;
        int embeddingDim = embeddings[0].Length;

        for (int pos = 0; pos < sequenceLength; pos++)
        {
            for (int i = 0; i < embeddingDim; i++)
            {
                double angle = pos / Math.Pow(10000, (2.0 * i) / embeddingDim);
                // Use sine for even dimensions, cosine for odd
                embeddings[pos][i] += (float)(i % 2 == 0 ? Math.Sin(angle) : Math.Cos(angle));
            }
        }

        return embeddings;
    }
}

Hoe Attentie werkt

  • Denk aan aandacht als vragen: "Om dit woord te vertalen, aan welke bronwoorden moet ik aandacht besteden?"
  • Het aandachtsmechanisme:
  • Vergelijkt de huidige doeltoestand met elk bronwoord

Berekent een "relevantiescore" voor elk bronwoord

Converteert scores naar waarschijnlijkheden (ze sommen op 1)

public class TransformerDecoder
{
    private WordEmbedding targetEmbedding;
    private SelfAttentionLayer[] selfAttentionLayers;
    private CrossAttentionLayer[] crossAttentionLayers;
    private FeedForwardLayer[] feedForwardLayers;

    public string[] Decode(float[][] encodedSource, int maxLength)
    {
        List<string> translation = new List<string>();
        translation.Add("<START>"); // Special token to begin

        while (translation.Count < maxLength)
        {
            // Get next word
            string nextWord = GenerateNextWord(encodedSource, translation.ToArray());

            if (nextWord == "<END>") break; // Stop token

            translation.Add(nextWord);
        }

        return translation.Skip(1).ToArray(); // Remove <START> token
    }

    private string GenerateNextWord(float[][] encodedSource, string[] partialTranslation)
    {
        // Step 1: Embed the partial translation
        float[][] targetEmbeddings = partialTranslation
            .Select(w => targetEmbedding.GetEmbedding(w))
            .ToArray();

        // Step 2: Self-attention on target words
        float[][] selfAttended = ApplySelfAttention(targetEmbeddings);

        // Step 3: Cross-attention to source (this is where translation happens!)
        float[][] crossAttended = ApplyCrossAttention(selfAttended, encodedSource);

        // Step 4: Feed forward
        float[] finalState = ApplyFeedForward(crossAttended[^1]); // Last position

        // Step 5: Predict next word
        return PredictWord(finalState);
    }

    private string PredictWord(float[] state)
    {
        // Convert state to probability distribution over all possible words
        Dictionary<string, double> wordProbabilities = CalculateWordProbabilities(state);

        // Return most likely word (or sample from distribution)
        return wordProbabilities.OrderByDescending(kv => kv.Value).First().Key;
    }
}

Maakt een gewogen gemiddelde van bronwoorden op basis van deze waarschijnlijkheden

sequenceDiagram
    participant Input as Source Sentence
    participant Encoder
    participant Decoder
    participant Output as Translation

    Input->>Encoder: "The cat sat"
    Encoder->>Encoder: Build representations
    Encoder->>Decoder: Encoded states

    Decoder->>Decoder: Generate [START]
    Decoder->>Output: Emit START token

    Decoder->>Decoder: Attend to "The" → Generate "Le"
    Decoder->>Output: "Le"

    Decoder->>Decoder: Attend to "cat" → Generate "chat"
    Decoder->>Output: "chat"

    Decoder->>Decoder: Attend to "sat" → Generate "s'est assis"
    Decoder->>Output: "s'est assis"

    Decoder->>Decoder: Generate [END]
    Output->>Output: "Le chat s'est assis"

Attentie visualiseren

Bij de vertaling van "De overeenkomst over de Europese Economische Ruimte werd in augustus 1992" naar het Duits ondertekend, ziet de aandacht er als volgt uit:

  1. **Bij het genereren van "Abkommen" (overeenkomst) besteedt het netwerk 90% aandacht aan "overeenkomst," 5% aan "de" en 5% verdeeld onder andere.**Zelf-Attentie: Aandacht voor jezelf
  2. Moderne transformatoren gebruikenzelfoplettendheid
  3. **: woorden in dezelfde zin houden elkaar bezig om betere voorstellingen op te bouwen.**In "De vis zwom in de buurt van de rivieroever" betekent het woord "bank" sterk "rivier," "vis" en "water" om het netwerk te helpen begrijpen dat het betekent "rivieroever" niet "financiële instelling."
public class TranslationTrainer
{
    private TransformerEncoder encoder;
    private TransformerDecoder decoder;
    private double learningRate = 0.0001;

    public void Train(ParallelCorpus corpus, int epochs)
    {
        foreach (var epoch in Enumerable.Range(0, epochs))
        {
            double totalLoss = 0;
            int batchCount = 0;

            foreach (var batch in corpus.GetBatches(batchSize: 32))
            {
                // Forward pass
                var predictions = new List<string[]>();
                var losses = new List<double>();

                foreach (var pair in batch)
                {
                    // Encode source
                    var encoded = encoder.Encode(pair.Source);

                    // Try to decode target
                    var predicted = decoder.Decode(encoded, pair.Target.Length);

                    // Calculate loss (how different is prediction from target?)
                    double loss = CalculateLoss(predicted, pair.Target);
                    losses.Add(loss);
                }

                // Backward pass: adjust weights
                double avgLoss = losses.Average();
                UpdateWeights(avgLoss);

                totalLoss += avgLoss;
                batchCount++;
            }

            Console.WriteLine($"Epoch {epoch}: Average Loss = {totalLoss / batchCount}");
        }
    }

    private double CalculateLoss(string[] predicted, string[] target)
    {
        // Cross-entropy loss: how far off were our word predictions?
        double loss = 0;

        for (int i = 0; i < Math.Min(predicted.Length, target.Length); i++)
        {
            if (predicted[i] != target[i])
            {
                loss += 1.0; // Simplified - real loss is more nuanced
            }
        }

        return loss / target.Length;
    }

    private void UpdateWeights(double loss)
    {
        // Backpropagation: adjust all weights in encoder and decoder
        // to reduce the loss (simplified here)
        // Real implementation uses automatic differentiation
    }
}

De Encoder-Decoder Architectuur

graph TD
    A[10M Sentence Pairs] --> B[Initial Random Weights]
    B --> C[Epoch 1: Loss = 5.2]
    C --> D[Epoch 2: Loss = 3.8]
    D --> E[Epoch 3: Loss = 2.1]
    E --> F[Epoch 10: Loss = 0.8]
    F --> G[Epoch 20: Loss = 0.3]
    G --> H[Trained Model!]

    style A stroke-width:2px
    style H stroke-width:4px

Nu kunnen we het allemaal in elkaar zetten!

  • Neurale machine vertaling maakt gebruik van eenencoder-decoder
  • **architectuur:**De Encoder: Het begrijpen van de Bron
  • **Het is de taak van de encoder om de bronzin te lezen en rijke representaties op te bouwen:**Na het coderen heeft elk bronwoord een voorstelling die het volgende vastlegt:

De betekenis (van de inbedding)

Zijn positie in de zin (van positionele codering)

graph TD
    subgraph "1. Encoding"
        A1[The] --> E1[emb: 0.2, -0.1, ...]
        A2[cat] --> E2[emb: 0.5, 0.3, ...]
        A3[sat] --> E3[emb: -0.1, 0.4, ...]
        A4[on] --> E4[emb: 0.1, -0.2, ...]
        A5[the] --> E5[emb: 0.2, -0.1, ...]
        A6[mat] --> E6[emb: 0.4, 0.2, ...]
    end

    subgraph "2. Self-Attention in Encoder"
        E1 & E2 & E3 & E4 & E5 & E6 --> SA[Self-Attention]
        SA --> C1[ctx: 0.3, 0.1, ...]
        SA --> C2[ctx: 0.6, 0.4, ...]
        SA --> C3[ctx: -0.2, 0.5, ...]
    end

    subgraph "3. Decoding"
        D1[START] --> G1[Le]
        C2 -.attend.-> G1

        G1 --> G2[chat]
        C2 -.attend.-> G2

        G2 --> G3[s'est assis]
        C3 -.attend.-> G3
    end


De relatie met andere woorden (van zelfoplettendheid)

De decoder: het genereren van de vertaling

var theEmbedding = encoder.GetEmbedding("The");
// [0.2, -0.1, 0.3, 0.05, ..., 0.1] (300 dimensions)

De decoder genereert de vertaling één woord tegelijk:

var catEmbedding = encoder.GetEmbedding("cat");
// [0.5, 0.3, -0.2, 0.4, ..., 0.15] (300 dimensions)

Het volledige vertaalproces:

// "cat" attends to other words
var catAttention = attention.CalculateAttention(catEmbedding, allWordEmbeddings);
// [0.1, 0.3, 0.2, 0.05, 0.1, 0.25]
// High attention to "sat" (0.3) and "mat" (0.25)

var catContextual = attention.ApplyAttention(catAttention, allWordEmbeddings);
// Weighted average incorporating context

Opleiding van een vertaalmodel

var decoderState = decoder.InitialState();

Het trainen van een vertaalmodel vereist drie dingen:

// Attend to source
var sourceAttention = crossAttention.Calculate(decoderState, encodedSource);
// [0.8, 0.05, 0.05, 0.02, 0.05, 0.03]
// Strong focus on "The" (0.8)

var nextWordProbs = decoder.PredictNextWord(decoderState, sourceAttention);
// {"Le": 0.85, "La": 0.08, "Les": 0.04, ...}

var firstWord = "Le";

Parallelle corpus

decoderState = decoder.UpdateState(decoderState, "Le");

var sourceAttention = crossAttention.Calculate(decoderState, encodedSource);
// [0.05, 0.9, 0.02, 0.01, 0.01, 0.01]
// Strong focus on "cat" (0.9)

var nextWordProbs = decoder.PredictNextWord(decoderState, sourceAttention);
// {"chat": 0.92, "chien": 0.03, ...}

var secondWord = "chat";

: Miljoenen zinsparen in beide talen

Final translation: "Le chat s'est assis sur le tapis"

Verliesfunctie

Hoe "fout" was onze voorspelling?

  1. Optimalisatie: Pas gewichten aan om het verlies te verminderen
  2. **Training vereist enorme hoeveelheden gegevens en berekeningen:**Voor een state-of-the-art model:
  3. Opleidingsgegevens: 10-100 miljoen strafparen
graph LR
    subgraph "Old: Recurrent Neural Networks"
        R1[Word 1] --> R2[Word 2]
        R2 --> R3[Word 3]
        R3 --> R4[Word 4]
    end

    subgraph "New: Transformers"
        T1[Word 1] -.attend.-> T2[Word 2]
        T1 -.attend.-> T3[Word 3]
        T1 -.attend.-> T4[Word 4]
        T2 -.attend.-> T3
        T2 -.attend.-> T4
        T3 -.attend.-> T4
    end


Opleidingstijd

: 1-4 weken op high-end GPU's

Modelgrootte

public class TranslationService
{
    private readonly HttpClient _httpClient;
    private readonly string _nmtServiceUrl;

    public TranslationService(HttpClient httpClient, IConfiguration config)
    {
        _httpClient = httpClient;
        _nmtServiceUrl = config["NMT:ServiceUrl"];
    }

    public async Task<TranslationResult> TranslateAsync(
        string text,
        string sourceLang,
        string targetLang)
    {
        var request = new TranslationRequest
        {
            Text = new[] { text },
            SourceLang = sourceLang,
            TargetLang = targetLang
        };

        var response = await _httpClient.PostAsJsonAsync(
            $"{_nmtServiceUrl}/translate",
            request);

        response.EnsureSuccessStatusCode();

        var result = await response.Content
            .ReadFromJsonAsync<TranslationResponse>();

        return new TranslationResult
        {
            Original = text,
            Translated = result.Translated[0],
            SourceLanguage = sourceLang,
            TargetLanguage = targetLang,
            TranslationTime = result.TranslationTime
        };
    }
}

public class TranslationRequest
{
    [JsonPropertyName("text")]
    public string[] Text { get; set; }

    [JsonPropertyName("source_lang")]
    public string SourceLang { get; set; }

    [JsonPropertyName("target_lang")]
    public string TargetLang { get; set; }
}

public class TranslationResponse
{
    [JsonPropertyName("translated")]
    public string[] Translated { get; set; }

    [JsonPropertyName("translation_time")]
    public double TranslationTime { get; set; }
}

: 100M tot 1B+ parameters (gewichten)

// In your controller or service
public class BlogPostController : ControllerBase
{
    private readonly TranslationService _translator;

    public async Task<IActionResult> TranslatePost(int postId, string targetLang)
    {
        var post = await _blogService.GetPostAsync(postId);

        var translatedTitle = await _translator.TranslateAsync(
            post.Title,
            "en",
            targetLang);

        var translatedContent = await _translator.TranslateAsync(
            post.Content,
            "en",
            targetLang);

        return Ok(new
        {
            Title = translatedTitle.Translated,
            Content = translatedContent.Translated,
            OriginalLanguage = "en",
            TargetLanguage = targetLang
        });
    }
}

Alles bij elkaar zetten: Een compleet voorbeeld

Laten we traceren door te vertalen "De kat zat op de mat" naar het Frans:

Stap-voor-stap proces:

public async Task<string> TranslateLongText(string longText, string targetLang)
{
    const int maxChunkSize = 500; // characters

    // Split on paragraph boundaries
    var paragraphs = longText.Split(new[] { "\n\n", "\r\n\r\n" },
        StringSplitOptions.RemoveEmptyEntries);

    var translatedParagraphs = new List<string>();

    foreach (var paragraph in paragraphs)
    {
        if (paragraph.Length <= maxChunkSize)
        {
            var result = await _translator.TranslateAsync(paragraph, "en", targetLang);
            translatedParagraphs.Add(result.Translated);
        }
        else
        {
            // Split long paragraph into sentences
            var sentences = SplitIntoSentences(paragraph);
            var translatedSentences = new List<string>();

            foreach (var sentence in sentences)
            {
                var result = await _translator.TranslateAsync(sentence, "en", targetLang);
                translatedSentences.Add(result.Translated);
            }

            translatedParagraphs.Add(string.Join(" ", translatedSentences));
        }
    }

    return string.Join("\n\n", translatedParagraphs);
}

Stap 1: Codeer "De"

Stap 2: Codeer "kat"

public async Task<string> TranslateMarkdown(string markdown, string targetLang)
{
    // Extract text from markdown while preserving structure
    var doc = Markdig.Markdown.Parse(markdown);
    var textSegments = new List<(string text, int position)>();

    // Walk the AST and extract translatable text
    foreach (var node in doc.Descendants())
    {
        if (node is LiteralInline literal)
        {
            var text = literal.Content.ToString();
            if (!string.IsNullOrWhiteSpace(text) && !IsImagePath(text))
            {
                textSegments.Add((text, literal.Span.Start));
            }
        }
    }

    // Translate all segments
    var translations = await Task.WhenAll(
        textSegments.Select(async seg => new
        {
            seg.position,
            translated = (await _translator.TranslateAsync(seg.text, "en", targetLang)).Translated
        }));

    // Reconstruct markdown with translations
    var result = markdown;
    foreach (var translation in translations.OrderByDescending(t => t.position))
    {
        result = result.Remove(translation.position, textSegments
            .First(s => s.position == translation.position).text.Length)
            .Insert(translation.position, translation.translated);
    }

    return result;
}

Stap 3: Zelf-aandacht

Stap 4: Decoder begint met

public async Task<Dictionary<string, string>> TranslateBatch(
    IEnumerable<string> texts,
    string targetLang)
{
    const int batchSize = 32;
    var results = new Dictionary<string, string>();

    foreach (var batch in texts.Chunk(batchSize))
    {
        var request = new TranslationRequest
        {
            Text = batch.ToArray(),
            SourceLang = "en",
            TargetLang = targetLang
        };

        var response = await _httpClient.PostAsJsonAsync(
            $"{_nmtServiceUrl}/translate",
            request);

        var result = await response.Content
            .ReadFromJsonAsync<TranslationResponse>();

        for (int i = 0; i < batch.Length; i++)
        {
            results[batch[i]] = result.Translated[i];
        }
    }

    return results;
}

Stap 5: Genereer "Le"

Stap 6: "chat" genereren

graph TD
    A[Translation Request] --> B{Model Size}
    B -->|Small 100M params| C[Fast: 50-100ms]
    B -->|Medium 500M params| D[Medium: 200-500ms]
    B -->|Large 1B+ params| E[Slow: 1-3 seconds]

    A --> F{Hardware}
    F -->|CPU| G[Slow: 2-5x slower]
    F -->|GPU| H[Fast: Baseline]
    F -->|TPU/Special AI chips| I[Very Fast: 2-3x faster]

    style C stroke-width:2px
    style E stroke-width:2px

Stap 7: Doorgaan tot

  • Waarom Transformers WonDe transformator architectuur (de "T" in ChatGPT!) werd dominant omdat:
  • Parallellering: In tegenstelling tot oudere terugkerende modellen, kunnen alle woorden tegelijkertijd worden verwerkt
  • Langeafstandsafhankelijkheden: Attentie kan elke twee woorden verbinden, ongeacht hoe ver uit elkaar

Schaalbaarheid: Meer gegevens + groter model = betere resultaten (tot een punt)

**Recurrente netwerken verwerken achtereenvolgens (langzaam!), terwijl transformatoren alle woorden tegelijk verwerken (snel!).**Gebruik van NMT in C#: Een praktisch voorbeeld

Nu je begrijpt hoe het werkt, hier is hoe je het zou gebruiken in een .NET applicatie:

Gebruik:

  1. Gemeenschappelijke uitdagingen en oplossingen1.
  2. Lange tekst verwerkenNMT-modellen hebben invoerlengtelimieten (meestal 512-1024 tokens).
  3. **Oplossing: wrijven!**2.
  4. Opmaak behoudenMarkdown, HTML en andere markeringen kunnen NMT-modellen verwarren:
  5. **3.**Batchverwerking voor efficiëntie

Eén zin tegelijk vertalen is traag.

Batch ze!

  • PrestatiekenmerkenHet begrijpen van de computationele kosten helpt u architect betere oplossingen:
  • **Typische prestaties (per zin op GPU):**Kleine modellen
  • (100M params): 50-100msMiddelgrote modellen
  • (500M params): 200-500mGrote modellen

(1B+ params): 1-3 seconden

Now when you hit "translate" on your blog posts, you'll know exactly what's happening under the hood! 🚀

CPU vs GPU

: GPU is 2-10x sneller voor NMT

  • Batchverwerking: Kan 100+ zinnen/seconde bereiken met batching
  • ConclusieNeurale machine vertaling lijkt misschien magisch, maar het is eigenlijk een elegante combinatie van verschillende slimme ideeën:
  • Inbeddingen: Represent words as vectors that capture means
  • Neurale netwerken: Leer patronen van miljoenen voorbeelden
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.