Comment fonctionne réellement la traduction par machine neuronale: Un développeur's Guide
Introduction
Si vous avez suivi ce blog, vous savez que j’ai un peu obsessé par la traduction automatique. en utilisant EasyNMT, Services de traduction fondés sur la construction, et même améliorer EasyNMT. Mais je me suis rendu compte que je n’avais jamais vraiment expliqué comment Travaux de traduction des machines neuronales sous le cape.
NOTE: Il s'agit d'une partie de mes expériences avec l'AI / une façon d'épargner $1000 Code Calude Crédits WebM SK3 J'ai alimenté cet article par un BUNCH de documentsMSC5 ma compréhensionMST6 des questions que j'ai eues pour générer cet articleMst7 C'est fun et remplit une lacune que je n'ai jamais vu remplie ailleursM st10
Alors, permettez-moi de corriger ce qui suit : ! Ce post vous permettra de passer de "la traduction est magiqueM SK3 à "la translation est une mathématique intelligente" en expliquant les concepts clés derrière la traduction en machine neuronale en anglais simple.
À la fin de ce post, vous comprendrez :
- Quels sont les réseaux neuronaux artificiels et comment ils apprennent
- Comment les mots deviennent des chiffres (embeddings)
- Qu’est-ce qu’on entend par « attention » dans l’AI?
- Comment fonctionne l'architecture de codeur-decoder
- Pourquoi les transformateurs ont pris le contrôle du monde
Ne vous inquiétez pas si vous n’êtes pas une personne de mathématiques.
L’idée de base: traduction en tant que croisement numérique
C’est là qu’il arrive que la machine neural de traduction traite la traduction comme un processus purement mathématique.
graph LR
A[Source Sentence] --> B[Convert to Numbers]
B --> C[Neural Network Processing]
C --> D[Convert to Words]
D --> E[Translated Sentence]
Mais attendez-vous - comment convertir les mots en chiffres? Et comment la mathématique " comprend ?
Réseaux neuronaux artificiels: La Fondation
Avant de comprendre la traduction neuronale par machine, nous devons comprendre les réseaux neuronaux artificiels.
Qu’est-ce qu’un neurone artificiel?
Un neurone artificiel est une fonction mathématique qui
- Prend plusieurs entrées
- Multiplie chaque entrée par un poids.
- Ajoute tous ces éléments.
- Applique une fonction de "activation" pour produire un résultat
Voici une représentation visuelle de '.
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
Let's faire ce béton avec un code CM SK1
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);
}
}
La fonction d'activation ( dans ce cas Math.TanhC’est ce qui rend les réseaux neuronaux intéressants.
De neurones à réseaux
La magie se produit lorsque vous reliez des milliers ou des millions de ces neurones en couches.
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
Chaque couche "réfère " la représentation de l'entrée, extrayant des patrons de plus en plus abstraitsM SK3 Dans la traduction
- Les premières couches pourraient détecter "il s’agit d’un verbeM SK1 ou "ce mot est passé tensé"
- Les couches moyennes pourraient détecter "il s’agit d’une questionM SK1 ou "il est sur le temps"
- Les couches ultérieures les combinent en " traduire cela comme une question polie sur demain
Comment les réseaux apprennent: Le processus de formation
C’est là la partie intelligente de ', nous ne mettons pas tous ces poids manuellement à l’essai.
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
Ce processus est appelé Écart des gradients. Pour chaque erreur de réponse, , le réseau détermine les poids qui étaient les plus responsables de cette erreur et les corrige un peu.
Voici une version simplifiée de C#:
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);
}
}
}
}
Les réseaux neuronaux réels utilisent un algorithme plus perfectionné appelé utilisation de la force antimissile qui calcule efficacement la façon d’ajuster les pondérations dans l’ensemble du réseau.
Embeddings de mots: Transformer la langue en mathématiques
Maintenant, nous avons atteint notre premier défi majeur : comment alimenter les mots dans un réseau neuronal? Nous devons convertir le texte en chiffres.
Le problème avec l'encodage One-Hot
L’approche naïve est "oneM SK1hot encoding" - assigner à chaque mot un numéro uniqueMSC4
// Don't do this!
var wordToNumber = new Dictionary<string, int>
{
{"cat", 1},
{"dog", 2},
{"king", 3},
{"queen", 4},
{"man", 5},
{"woman", 6}
};
Mais cela pose un énorme problème.
La solution: Embeddings de mots
En revanche, nous représentons chaque mot comme un Le vecteur de nombres, typiquement 300-1000 dimensionsM SK2 Mots similaires obtiennent des vecteurs semblables:
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));
}
}
Voici ce qui rend les embeddings magiques : ils captent des relations sémantiques.
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
Le célèbre exemple: 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"
}
Comment on apprend les embeddings?
Les embeddings sont appris par la formation d’un réseau neuronal à une tâche simple.
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, ...]"]
Après une formation sur des milliards de phrases, les mots qui apparaissent dans des contextes similaires se terminent par des embeddings semblables.
Attention: Changeur de jeu
Voici un aperçu essentiel de : lorsqu’il s’agit de traduire :
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
Attention est un mécanisme qui permet au réseau "focus" de se concentrer sur différentes parties de l’entrée lors de la production de chaque mot de sortieM SK2
Comment l'attention fonctionne
Pensez à l’attention comme s’il s’agissait de demander :
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;
}
}
Mécanisme d’attention:
- Comparaison de l'état cible actuel à chaque mot source
- Calcule un score de "relevance" pour chaque mot source
- Convert les notes en probabilités (la somme de ces valeurs à 1)
- Crée une moyenne pondérée des mots de source en fonction de ces probabilités
Visualiser l'attention
Lors de la traduction " L’accord sur l’Espace économique européen a été signé en août 1992" à l’allemand
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
Lors de la génération "AbkommenM SK1 (agreement),, le réseau accorde l’attention SSK4 à "agrémentMSC6 \ \ 5% aux S"à ", et \ 5%, réparties entre autres mots .
Personne-même-Attention: Attendue à soi
Utilisation des transformateurs modernes auto-attention: les mots dans la même phrase s’assurent mutuellement pour bâtir de meilleures représentations
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
Dans "Le poisson s'écoulait près de la rive du fleuve",, le mot "banqueM SK3 est fortement associé à "riverMSC5 M" poissonsMNK7 et P"eauMRK9 aidant le réseau à comprendre ce qui signifie R"rivebankMMK11 non "institution financièreMZK13
Architecture de l'encoder-Decoder
Maintenant, nous pouvons les mettre tous ensemble! La traduction par machine neuronale utilise un codeur-décoder architecture:
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
Encoder: Comprendre la source
L’encoder' a pour tâche de lire la phrase source et d’établir des représentations riches.
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;
}
}
Après l’encodage,, chaque mot source a une représentation qui capte
- Sa signification (de l'incorporation)
- Sa position dans la phrase (de l'encodage positionnelM SK1
- Sa relation avec d'autres mots (de moi-même
Le décoder: Générer la traduction
Le décodeur génère la traduction d’une seule parole à la fois:
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;
}
}
Le processus de traduction complet:
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"
Formation d'un modèle de traduction
La formation d’un modèle de traduction exige trois choses:
- Corps parallèle: Des millions de paires de phrases dans les deux langues
- Function de perte: Comment notre prédiction était-elle ?
- Optimisation: Adjustez les poids pour réduire la perte
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
}
}
La formation nécessite des quantités énormes de données et de calculs:
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
Pour un État-deM SK1le-modèle d'art :
- Données de formation: 10-100 millions de paires
- Temps de formation: 1-4 semaines sur GPUs de haut niveau
- Taille du modèleParamètres (poidsM SK5
Mettre tout ensemble: Un exemple complet
Let's trace through translating "The cat sat on the matM SK2 to French:
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 --> SA[Self-Attention]
E2 --> SA
E3 --> SA
E4 --> SA
E5 --> SA
E6 --> SA
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
Étape-par-Procédure d'étape :
Étape 1: Encoder "The"
var theEmbedding = encoder.GetEmbedding("The");
// [0.2, -0.1, 0.3, 0.05, ..., 0.1] (300 dimensions)
Étape 2: Codage "catM SK2
var catEmbedding = encoder.GetEmbedding("cat");
// [0.5, 0.3, -0.2, 0.4, ..., 0.15] (300 dimensions)
Étape 3: Automne -Attention
// "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
Étape 4: Décoder débute avec
var decoderState = decoder.InitialState();
Étape 5: Générer "LeM SK2
// 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";
Étape 6: Générer "chatM SK2
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";
Étape 7: Poursuivre jusqu’à
Final translation: "Le chat s'est assis sur le tapis"
Pourquoi les transformateurs ont gagné
L’architecture de transformateur (le "TM SK2 dans ChatGPT!) est devenu dominante parceMSC4
- Parallèlement: Contrairement aux modèles récurrents plus anciens, tous les mots peuvent être traités simultanément
- Long-dépendances de portée: L’attention peut relier n’importe quelles deux mots , peu importe la distance
- Étalonnabilité: Plus de données + Modèle plus grand MSC2 meilleurs résultats MSC3 jusqu'à un point
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
Les réseaux renouvelables procèdent de façon séquentielle (slowM SK1 alors que les transformateurs procèderont à toutes les mots en même temps (fast!).
Utilisation de la NMT dans le C#: Un exemple pratique
Maintenant que vous comprenez comment cela fonctionne, il s’agit ici de l’application NET.
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; }
}
Usage:
// 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
});
}
}
Défis et solutions communs
1. Traitement de textes longs
Les modèles NMT ont des limites de longueur d’entrée (typiquement 512-1024 tokensM SK2 SolutionMSC3 chunking!
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);
}
2. Préservation du formattage
Markdown, HTML, et d’autres marquages peuvent confondre les modèles NMT
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;
}
3. Traitement en lots pour efficacité
La traduction d’une phrase à la fois est lente.
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;
}
Caractéristiques du rendement
La compréhension des coûts de calcul vous aide à concevoir de meilleures solutions:
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
Rendement typique (par phrase sur GPU):
- Petits modèles (100M paramsM SK1 50-100ms
- Modèles moyens (500M paramsM SK1 200-500ms
- Grands modèles (1BM SK1 params): 1-3 secondes
CPU vs GPU: GPU est plus rapide pour NMT
Traitement par lots: Peut obtenir 100+ phrasesM SK2 seconde avec le battage
Conclusion
La traduction par machine neuronale peut sembler magique, mais il s’agit en fait d’une combinaison élégante de plusieurs idées intelligentes.
- Embeddings: Représenter les mots comme vecteurs qui captent le sens
- Réseaux neuronaux: Apprendre des modèles à partir de millions d'exemples
- Attention: Concentration sur les parties pertinentes de l'entrée
- Encoder-decoder: Comprendre séparément de la génération
- Transformateurs: Traiter tout en parallèle
Le plus marquant est que les systèmes NMT modernes n’ont pas de règles explicites sur la grammaire.
Si vous ' travaillez avec NMT dans la production ( comme je le fais pour ce blog
- Questions de débug: Pourquoi le modèle traduit-t-il "banqueM SK2 comme "banc" S(financierMNK6 au lieu de SSK7riveMSC8 |
- Optimiser la performance: Dois-je faire des demandes de lot?M SK1 Utiliser la recherche par faisceau?
- Appuyer les cases à bord: Comment préserver les blocs de code dans le marquage traduit
- Établir les attentes: Pourquoi ne peut-on pas manipuler les documents de mots '?
Le domaine continue d’évoluer rapidement. Les modèles deviennent plus grands, plus rapidesM SK2 et meilleurs tous les ans . Mais les concepts fondamentaux MST4 embeddings MST5 attentionM ST6 encoder M ST7 décoder M st8 demeurent fondamentalesM st9
Maintenant, lorsque vous cliquez sur "traduire " dans vos articles de blog, vousM SK3pourriez savoir exactement ce qui se passe sous le capuchon.
Autres lectures
Si vous voulez plonger plus loin:
- Attention est tout ce dont vous avez besoin - Le papier original du transformateur
- Le transformateur illustré - Excellentes explications visuelles
- Traduction en machine neuronale par l'apprentissage conjoint d'aligner et de traduire - Document sur le mécanisme d'attention
- Mon article sur EasyNMT - Détails sur la mise en œuvre pratique