Extraction simple de la fonction OCR et NER dans C# avec ONNX
Comme I' l’a construit lucideRAG I'm lire les médias sociaux où les gens continuent de demander la même chose . M SK2Comment obtenir des fonctionnalités du texte scanné ?' l'erreur de catégorie est toujours MSC4juste utiliser un LLMMST5qui fonctionne mais est très coûteux MST6 Alors que jeMst7m déjà en profondeur dans l'espace OCR J’ai pensé que j’aurais écrit une approche 'à l’intention des débutants ' à l’approche non-conventionnelle M SK3LLM (ou LLM facultative MSC5 pour le faire.
Vous avez des images avec un texte. Vous voulez extraire ce texte , puis trouver la structure utile à l'intérieur de celui-ci ( nomsM SK3 sociétés
Cet article montre les la plus simple possible pipeline: Tesseract pour extraction de texte, alors BERT NER (par le biais de l’ONNX) pour la reconnaissance des entités . Tous les locauxMSC3 Tous déterministesM SK4 Tous dans CCSK5
Le terme déterministe signifie ici des versions fixes, données linguistiques fixes≥, et aucun apprentissage adaptatif à l’heure d’exécution.
Besoin de la version emballée? Ce pipeline est maintenant un paquet NuGet avec l'auto-downloading models, OpenCV preprocessingMSC2 FlorenceM SK3 visionMST4 et un outil CLIMSL5 Voir Partie 2: Le paquet NuGet pour la version prête-àM SK1pour l'utilisation.
Le pipeline complet
flowchart LR
subgraph OCR["Part 1: OCR"]
IMG[Image]
TESS[Tesseract]
TXT[Raw Text]
end
subgraph NER["Part 2: NER"]
TOK[Tokenize]
BERT[BERT NER<br/>ONNX]
ENT[Entities]
end
IMG --> TESS
TESS --> TXT
TXT --> TOK
TOK --> BERT
BERT --> ENT
style TESS stroke:#f60,stroke-width:3px
style BERT stroke:#f60,stroke-width:3px
style ENT stroke:#090,stroke-width:3px
Deux étapes, deux modèles , tous deux fonctionnent localementM SK2 Laissez' construire chaque partieMSC4
Partie 1: OCR avec Tesseract
Tesseract est le moteur OCR de source standard open - . Nous utilisons Tesseract.NET, un enveloppeur CM SK1
dotnet add package Tesseract
Vous avez également besoin des fichiers de données formés. Télécharger eng.traineddata de Les données de l'essai sont les suivantes : et le placer dans un tessdata dossier.
using Tesseract;
public static string ExtractText(string imagePath)
{
using var engine = new TesseractEngine("./tessdata", "eng", EngineMode.Default);
using var img = Pix.LoadFromFile(imagePath);
using var page = engine.Process(img);
return page.GetText();
}
Il s’agit là d’un appel ExtractText("invoice.png") et vous obtiendrez une chaîne
Importance:
TesseractEngineest coûteux de créer. Dans les applications réelles, le créer une fois et l’utiliser à nouveau
Limitations
Tesseract fonctionne bien pour nettoyer, hautM SK1 texte contrastant dans les caractères standard. Il se heurte à la difficulté :
- Fonts stylisés ou décoratifs
- Scans ou photos de faible qualité-
- Texte tourné ou courbé
- Texte sur des fonds complexes
- GIF animés avec sous-titres
- Frais de ligne (
inter-\nnational) peut nécessiter un traitement après - avant le NER
Pour les systèmes de production qui doivent traiter des objets étranges, voir Le pipeline Three-Tier OCR-qui ajoute FlorenceM SK1 ONNX comme niveau moyen et escalation Vision LLM pour les cas durs.
Pour ce tutorial, nous présumons que vous avez des images ou un texte nettoyés d'une autre source.
Dans la pratique, vous voudrez normalement normaliser le résultat de l'OCR M SK1 trim whitespace , collapse repeated newlines, fix obvious hyphenationMNK4 avant de le transmettre à NERMSC5
Partie 2: NER avec ONNX
Pourquoi cette approche fonctionne?
Avant de se plonger dans le code, laissez-leur comprendre ce que nous faisons en réalité.
Qu'est-ce que NER?
Reconnaissance des entités désignées (NER) est un problème résolu. Les chercheurs ont formé des réseaux neuronaux capables de lire le texte et de mettre en évidence les "bits intéressants ":
- PER - Nom de la personne "John Smith", MSC3DrMSC4 Jane DoeM SK5
- ORG - Organisations "Microsoft", "NHSM SK4 |
- LOC - Endroits "Londres", "Mount EverestM SK4
- MISC - Autres entités
Le modèle ne comprend pas les modèles statistiques tirés de millions d’exemples étiquetés. NER est extraction de la caractéristique, non raisonnement. Il correspond à ce qui est observé sur les stéroïdes.
Pourquoi ONNX?
ANNEXE I (Open Neural Network Exchange) est un format standard pour les modèles M.L. frozen inference DLL pour les réseaux neuronaux: poids fixes dans l'enceinteM SK1 tenseurs hors de celle-ci , aucune logique d'entraînement, aucun caractère aléatoireMSC4
flowchart LR
subgraph Training["Training (Python)"]
PT[PyTorch Model]
TF[TensorFlow Model]
end
subgraph Export["Export Once"]
ONNX[model.onnx]
end
subgraph Runtime["Run Anywhere"]
CS[C# App]
CPP[C++ App]
JS[JavaScript App]
end
PT --> ONNX
TF --> ONNX
ONNX --> CS
ONNX --> CPP
ONNX --> JS
style ONNX stroke:#f60,stroke-width:4px
style CS stroke:#090,stroke-width:3px
Le point de vue clé: un autre a fait le travail ardu (formation du modèle en Python). Vous faites simplement l'inférence dans CM SK2
Pourquoi ne pas simplement utiliser un LLM?
Vous pouvez envoyer un texte à GPT-4 et demander M SK1Trouver les personnes et entreprises dans ce texte ". Il fonctionne! MaisMSC4
| Approche | Accès rapide MSC2 Coût par dossier SMC3 Dossiers SMC4 Protection des renseignements personnels ESC5 Consistence ESC6 | |||||
|---|---|---|---|---|---|---|
| TÉU, TPAC, TPMD, TM, TMÉU, TC: | ~50ms | |||||
| LLM API local | 4-30s | $0 | Les petits modèles peuvent être floqués \ | Variable \ | ||
| LLM API | 1-5s | $20-50 | Données envoyées à l'extérieur S | Variable M |
Les LLM sont excellents pour des raisonnements complexes. Pour l’extraction de modèles à grande échelleM SK1 un modèle spécial est 40x plus rapide et gratuitMSC3
Le pipeline
Voici ce que nous construisons.
flowchart LR
subgraph Input
TEXT[Raw Text]
end
subgraph Tokenization["Step 1: Tokenization"]
TOK[Split into tokens]
IDS[Convert to IDs]
end
subgraph Model["Step 2: ONNX Inference"]
BERT[BERT Model]
LOGITS[Logits Output]
end
subgraph Output["Step 3: Decode"]
LABELS[BIO Labels]
ENT[Entities]
end
TEXT --> TOK
TOK --> IDS
IDS --> BERT
BERT --> LOGITS
LOGITS --> LABELS
LABELS --> ENT
style BERT stroke:#f60,stroke-width:4px
style ENT stroke:#090,stroke-width:3px
Chaque étape est simple.
Étape 1: Télécharger le modèle
Vous avez besoin de trois fichiers de HuggingFace. Téléchargez-les manuellement dans un dossier M SK1e .g., ./models/ner/):
| Fichier | Taille | |
|---|---|---|
model.onnx |
~430MB | Télécharger |
vocab.txt |
~230KB | Télécharger |
config.json |
~1KB | Télécharger |
Le modèle est Bert-base-NER exporté au format ONNX par Protection.
Votre dossier devrait ressembler à:
models/
ner/
model.onnx (the neural network)
vocab.txt (word → ID mapping)
config.json (label definitions)
Étape 2: Établissement du projet
Créer une nouvelle application de console et ajouter les paquets NuGet:
dotnet new console -n NerDemo
cd NerDemo
dotnet add package Microsoft.ML.OnnxRuntime
dotnet add package Microsoft.ML.Tokenizers
Il s’agit de deux paquets.
- OnnxRuntime - Exécute le modèle
- ML.Tokenizers - Génie le texte → conversion de cote
Étape 3: Comprendre la tokenisation
Avant que le modèle puisse traiter le texte, nous devons le convertir en chiffres. C'est appelé Tokenisation.
flowchart TD
subgraph Input
TEXT["John works at Microsoft"]
end
subgraph Tokenize["Tokenization"]
T1["[CLS]"]
T2["John"]
T3["works"]
T4["at"]
T5["Microsoft"]
T6["[SEP]"]
end
subgraph IDs["Token IDs"]
I1["101"]
I2["1287"]
I3["2573"]
I4["1120"]
I5["7513"]
I6["102"]
end
TEXT --> T1 & T2 & T3 & T4 & T5 & T6
T1 --> I1
T2 --> I2
T3 --> I3
T4 --> I4
T5 --> I5
T6 --> I6
style T1 stroke:#c00,stroke-width:3px
style T6 stroke:#c00,stroke-width:3px
style I2 stroke:#090,stroke-width:3px
style I5 stroke:#090,stroke-width:3px
Points clés:
[CLS]et[SEP]sont des tokens spéciaux qui marquent les limites de la phrase- Chaque mot devient un nombre de
vocab.txt - Le modèle ne voit que les chiffres, jamais le texte réel
Chargement du Tokenizer
using Microsoft.ML.Tokenizers;
// Load the vocabulary file
var vocabPath = "./models/ner/vocab.txt";
var options = new BertOptions
{
LowerCaseBeforeTokenization = false, // BERT-NER is case-sensitive!
UnknownToken = "[UNK]",
ClassificationToken = "[CLS]",
SeparatorToken = "[SEP]",
PaddingToken = "[PAD]"
};
using var stream = File.OpenRead(vocabPath);
var tokenizer = BertTokenizer.Create(stream, options);
Pourquoi? LowerCaseBeforeTokenization = false? Ce modèle a été formé sur un texte en case.
Importance: Le tokeniseur Il faut correspondre exactement au modèle. Utilisation d'un vocab différentM SK1 option de caisse, ou des identifiants spéciaux pour le symbole dégradera silencieusement les résultats . Utiliser toujours
vocab.txtque les navires sont équipés du modèle
Tokeniser le texte
var text = "John Smith works at Microsoft in London.";
// Tokenize (splits into subwords)
var encoded = tokenizer.EncodeToTokens(text, out _);
// Get special token IDs
var clsId = 101; // [CLS] token
var sepId = 102; // [SEP] token
// Build the full sequence: [CLS] + tokens + [SEP]
var tokenIds = new List<int> { clsId };
tokenIds.AddRange(encoded.Select(t => t.Id));
tokenIds.Add(sepId);
// Also keep the text tokens for later
var tokens = new List<string> { "[CLS]" };
tokens.AddRange(encoded.Select(t => t.Value));
tokens.Add("[SEP]");
Après cela, nous avons
tokenIds:[101, 1287, 3455, 2573, 1120, 7513, 1999, 2414, 119, 102]tokens:["[CLS]", "John", "Smith", "works", "at", "Microsoft", "in", "London", ".", "[SEP]"]
Étape 4: Faire fonctionner le modèle
Maintenant, nous envoyons ces chiffres dans le modèle ONNX. Le modèle retourne "logits"-cotes brutes pour chaque étiquette possible à chaque positionM SK3
flowchart LR
subgraph Inputs
IDS["Token IDs<br/>[101, 1287, 3455, ...]"]
MASK["Attention Mask<br/>[1, 1, 1, ...]"]
end
subgraph Model
ONNX["BERT NER<br/>model.onnx"]
end
subgraph Outputs
LOG["Logits<br/>[batch, seq_len, 9]"]
end
IDS --> ONNX
MASK --> ONNX
ONNX --> LOG
style ONNX stroke:#f60,stroke-width:4px
Déchargement du modèle
using Microsoft.ML.OnnxRuntime;
using Microsoft.ML.OnnxRuntime.Tensors;
// Configure for best performance
var sessionOptions = new SessionOptions
{
GraphOptimizationLevel = GraphOptimizationLevel.ORT_ENABLE_ALL,
IntraOpNumThreads = Math.Min(4, Environment.ProcessorCount)
};
// Load the model (takes ~2 seconds first time)
var session = new InferenceSession("./models/ner/model.onnx", sessionOptions);
Importance: Créer le tokenizer et la session d'inférence une fois (singletonMSC2service) et les réutiliserM SK4 DonMST5 ne recharger pas par document
InferenceSessionest coûteux de créer.
Préparation des intrants
Le modèle prévoit:
- input_ids: Nos numéros d'identification comme
long[] - attention_masque: 1 pour les tokens réelsM SK2 ≥0 pour le padding
// Pad to a fixed length (BERT requires fixed shapes; powers of 2 are cache-friendly)
int sequenceLength = 64; // or 128, 256, 512
var inputIds = new long[sequenceLength];
var attentionMask = new long[sequenceLength];
for (int i = 0; i < sequenceLength; i++)
{
if (i < tokenIds.Count)
{
inputIds[i] = tokenIds[i];
attentionMask[i] = 1;
}
else
{
inputIds[i] = 0; // PAD token
attentionMask[i] = 0;
}
}
Inférence en cours
// Create tensors (shape: [batch_size=1, sequence_length])
var inputIdsTensor = new DenseTensor<long>(inputIds, [1, sequenceLength]);
var attentionMaskTensor = new DenseTensor<long>(attentionMask, [1, sequenceLength]);
// Build inputs
var inputs = new List<NamedOnnxValue>
{
NamedOnnxValue.CreateFromTensor("input_ids", inputIdsTensor),
NamedOnnxValue.CreateFromTensor("attention_mask", attentionMaskTensor)
};
// Run the model
using var results = session.Run(inputs);
// Get output logits
var output = results.First(r => r.Name == "logits");
var logits = output.AsTensor<float>();
Le résultat logits a une forme [1, sequence_length, 9]-9 étiquettes possibles pour chaque position de coteM SK1
Étape 5: Décodage de la sortie
Le modèle produit des cotes brutes. Nous devons
- Trouver l'étiquette de cotation la plus élevée- pour chaque token
- Convertir ces étiquettes en entités réelles
Comprendre les étiquettes BIO
Le modèle utilise Notation BIO:
flowchart LR
subgraph Tokens
T1["John"]
T2["Smith"]
T3["works"]
T4["at"]
T5["Microsoft"]
end
subgraph Labels
L1["B-PER"]
L2["I-PER"]
L3["O"]
L4["O"]
L5["B-ORG"]
end
T1 --> L1
T2 --> L2
T3 --> L3
T4 --> L4
T5 --> L5
style L1 stroke:#090,stroke-width:3px
style L2 stroke:#090,stroke-width:3px
style L5 stroke:#00f,stroke-width:3px
- B-PER = Début d’une entité de personnes
- I-PER = À l’intérieur d’une entité de Personne (suiteM SK2
- O = À l’extérieur de toute entité
- B-ORG = Début d’une organisation
Cela permet au modèle de manipuler des entités multiples de mots comme "John SmithM SK2 ou "United Kingdom".
La cartographie des étiquettes
// These are the 9 labels from the protectai/bert-base-NER-onnx config.json (id2label field)
string[] labels =
{
"O", // 0: Outside any entity
"B-MISC", // 1: Beginning of Miscellaneous
"I-MISC", // 2: Inside Miscellaneous
"B-PER", // 3: Beginning of Person
"I-PER", // 4: Inside Person
"B-ORG", // 5: Beginning of Organization
"I-ORG", // 6: Inside Organization
"B-LOC", // 7: Beginning of Location
"I-LOC" // 8: Inside Location
};
Remarque: Ce modèle spécifique utilise le schéma coNLL de l’étiquette standard 9-.
config.json(id2labelchamp). Si vous échangez des modèlesM SK1 lire les étiquettes de la configuration plutôt que du codage dur.
Trouver le meilleur étiquette
Pour chaque token, nous choisissons l’étiquette ayant le score logit le plus élevé:
var predictions = new List<(string Token, string Label, float Confidence)>();
int numLabels = 9;
for (int i = 0; i < tokens.Count; i++)
{
// Skip special tokens
if (tokens[i] is "[CLS]" or "[SEP]" or "[PAD]")
continue;
// Find highest scoring label
float maxScore = float.MinValue;
int maxIndex = 0;
for (int j = 0; j < numLabels; j++)
{
float score = logits[0, i, j];
if (score > maxScore)
{
maxScore = score;
maxIndex = j;
}
}
// Convert logit to probability (softmax)
float confidence = Softmax(logits, i, numLabels, maxIndex);
predictions.Add((tokens[i], labels[maxIndex], confidence));
}
L'article 5 de la Loi sur l'immigration et le statut des réfugiés Softmax fonction convert les cotes brutes en probabilités (0-1):
static float Softmax(Tensor<float> logits, int position, int numLabels, int targetIndex)
{
// Find max for numerical stability
float maxLogit = float.MinValue;
for (int j = 0; j < numLabels; j++)
maxLogit = Math.Max(maxLogit, logits[0, position, j]);
// Compute softmax
float sumExp = 0f;
for (int j = 0; j < numLabels; j++)
sumExp += MathF.Exp(logits[0, position, j] - maxLogit);
return MathF.Exp(logits[0, position, targetIndex] - maxLogit) / sumExp;
}
Note sur la confiance: Les scores de Softmax sont relatives, probabilités non calibrées . EllesM SK2 sont utiles pour le classement et la détermination des seuils, mais ne traitent pas
0.92en tant que "92% correct ". Utilisez-les pour filtrer les prédictions de confiance faiblesM SK2, non comme la vérité fondéeMSC4
Étape 6: Extraire les entités
Nous devons les fusionner en entités.
Premièrement, les aides à la fusion WordPiece. Ces manœuvres ## correctement les sous- mots et l’espacement de la ponctuation:
static void AppendWordPiece(StringBuilder sb, string token)
{
if (string.IsNullOrEmpty(token)) return;
// WordPiece continuation: "##soft" → append without space
if (token.StartsWith("##", StringComparison.Ordinal))
{
sb.Append(token.AsSpan(2));
return;
}
// No leading space if first token or if punctuation
if (sb.Length > 0 && !IsPunctuationToken(token))
sb.Append(' ');
sb.Append(token);
}
static bool IsPunctuationToken(string token) =>
token.Length == 1 && char.IsPunctuation(token[0]);
static string MergeWordPieces(IEnumerable<string> tokens)
{
var sb = new StringBuilder();
foreach (var t in tokens)
AppendWordPiece(sb, t);
return sb.ToString();
}
Maintenant, l’extraction de l’entité. La confiance de l'entité est la Confiance minimale en symboles sur l’ensemble de la gamme-conservatifM SK1 donc un seul symbole faible n’est pas caché par une évaluation moyenne
public sealed class Entity
{
public required string Text { get; init; }
public required string Type { get; init; } // PER, ORG, LOC, MISC
public required float Confidence { get; init; }
}
static List<Entity> ExtractEntities(
IReadOnlyList<(string Token, string Label, float Confidence)> predictions)
{
var entities = new List<Entity>();
string? currentType = null;
var currentTokens = new List<string>();
float currentConfidence = 1.0f;
void Flush()
{
if (currentType == null || currentTokens.Count == 0) return;
entities.Add(new Entity
{
Type = currentType,
Text = MergeWordPieces(currentTokens),
Confidence = currentConfidence
});
currentType = null;
currentTokens.Clear();
currentConfidence = 1.0f;
}
foreach (var (token, label, conf) in predictions)
{
// Continue current entity if model says I-<same type>
if (currentType != null && label == $"I-{currentType}")
{
currentTokens.Add(token); // keep ## form, merge handles it
currentConfidence = Math.Min(currentConfidence, conf);
continue;
}
// New entity begins
if (label.StartsWith("B-", StringComparison.Ordinal))
{
Flush();
currentType = label[2..];
currentTokens.Add(token);
currentConfidence = conf;
continue;
}
// Anything else (O, or I- without matching current type) ends the entity
Flush();
}
Flush();
return entities;
}
Note: WordPiece est géré entièrement par MergeWordPieces-aucun flux de contrôle spécial n'est nécessaireM SK1
Exemple complète
Voici un exemple de travail minimal que vous pouvez copier et exécuter:
using System.Text;
using Microsoft.ML.OnnxRuntime;
using Microsoft.ML.OnnxRuntime.Tensors;
using Microsoft.ML.Tokenizers;
// === Configuration ===
var modelPath = "./models/ner/model.onnx";
var vocabPath = "./models/ner/vocab.txt";
// === Load tokenizer ===
var bertOptions = new BertOptions
{
LowerCaseBeforeTokenization = false,
UnknownToken = "[UNK]",
ClassificationToken = "[CLS]",
SeparatorToken = "[SEP]"
};
using var vocabStream = File.OpenRead(vocabPath);
var tokenizer = BertTokenizer.Create(vocabStream, bertOptions);
// === Load model ===
var sessionOptions = new SessionOptions
{
GraphOptimizationLevel = GraphOptimizationLevel.ORT_ENABLE_ALL
};
using var session = new InferenceSession(modelPath, sessionOptions);
// === Process text ===
var text = "John Smith, CEO of Microsoft, announced the acquisition in London yesterday.";
// Tokenize
var encoded = tokenizer.EncodeToTokens(text, out _);
// Build sequence with special tokens
var tokens = new List<string> { "[CLS]" };
tokens.AddRange(encoded.Select(t => t.Value));
tokens.Add("[SEP]");
var rawIds = new List<int> { 101 }; // [CLS]
rawIds.AddRange(encoded.Select(t => t.Id));
rawIds.Add(102); // [SEP]
// Pad to fixed length
int seqLen = 64;
var inputIds = new long[seqLen];
var attentionMask = new long[seqLen];
for (int i = 0; i < seqLen; i++)
{
inputIds[i] = i < rawIds.Count ? rawIds[i] : 0;
attentionMask[i] = i < rawIds.Count ? 1 : 0;
}
// === Run inference ===
var inputs = new List<NamedOnnxValue>
{
NamedOnnxValue.CreateFromTensor("input_ids",
new DenseTensor<long>(inputIds, [1, seqLen])),
NamedOnnxValue.CreateFromTensor("attention_mask",
new DenseTensor<long>(attentionMask, [1, seqLen]))
};
using var results = session.Run(inputs);
var logits = results.First().AsTensor<float>();
// === Decode predictions ===
string[] labels = ["O", "B-MISC", "I-MISC", "B-PER", "I-PER", "B-ORG", "I-ORG", "B-LOC", "I-LOC"];
// WordPiece merge helper
static string MergeWordPieces(List<string> tokens)
{
var sb = new StringBuilder();
foreach (var t in tokens)
{
if (t.StartsWith("##", StringComparison.Ordinal))
sb.Append(t.AsSpan(2));
else if (sb.Length > 0 && t.Length > 0 && !char.IsPunctuation(t[0]))
sb.Append(' ').Append(t);
else
sb.Append(t);
}
return sb.ToString();
}
Console.WriteLine($"Input: {text}\n");
Console.WriteLine("Entities found:");
string? currentType = null;
var currentTokens = new List<string>();
for (int i = 1; i < tokens.Count - 1; i++) // Skip [CLS] and [SEP]
{
var token = tokens[i];
// Find best label
int bestIdx = 0;
float bestScore = float.MinValue;
for (int j = 0; j < 9; j++)
{
if (logits[0, i, j] > bestScore)
{
bestScore = logits[0, i, j];
bestIdx = j;
}
}
var label = labels[bestIdx];
// Continue current entity if model says I-<same type>
if (currentType != null && label == $"I-{currentType}")
{
currentTokens.Add(token);
continue;
}
// New entity begins
if (label.StartsWith("B-"))
{
if (currentType != null)
Console.WriteLine($" [{currentType}] {MergeWordPieces(currentTokens)}");
currentType = label[2..];
currentTokens = [token];
continue;
}
// Anything else ends the current entity
if (currentType != null)
Console.WriteLine($" [{currentType}] {MergeWordPieces(currentTokens)}");
currentType = null;
currentTokens.Clear();
}
// Output last entity
if (currentType != null)
Console.WriteLine($" [{currentType}] {MergeWordPieces(currentTokens)}");
Output:
Input: John Smith, CEO of Microsoft, announced the acquisition in London yesterday.
Entities found:
[PER] John Smith
[ORG] Microsoft
[LOC] London
Comprendre les sous- mots de WordPiece
BERT utilise Tokenisation WordPiece, qui divise les mots inconnus en sous- mots ## Avant-propos signifie "suite du mot précédent":
flowchart LR
subgraph Original
W1["Elasticsearch"]
end
subgraph Tokenized
T1["Elastic"]
T2["##search"]
end
subgraph Merged
M1["Elasticsearch"]
end
W1 --> T1 & T2
T1 --> M1
T2 --> M1
style T2 stroke:#c00,stroke-width:3px
L'article 5 de la Loi sur l'immigration et le statut des réfugiés MergeWordPieces helper gère ce: ## les tokens sont ajoutés sans espace, produisant "Elasticsearch" au lieu de "Elastic search".
Conseils sur la performance
1. Batch de textes multiples
Si vous avez de nombreux textes, les traiter en lots:
// Instead of: 1 text × 1 inference = 50ms
// Do: 16 texts × 1 inference = 100ms (6ms per text)
int batchSize = 16;
var batchInputIds = new long[batchSize, seqLen];
// ... fill batch ...
var tensor = new DenseTensor<long>(batchInputIds, [batchSize, seqLen]);
2. Utilisation de bouclage intelligent
Utilisez le bol le plus petit qui convient.
int[] buckets = [32, 64, 128, 256, 512];
int targetLength = buckets.FirstOrDefault(b => b >= tokenIds.Count);
if (targetLength == 0) targetLength = 512;
Dans la pratique, ONNX NER est assez rapide pour fonctionner en ligne pendant l'ingestion, pas seulement comme un travail en lots. Vous pouvez extraire des entités à mesure que les documents arrivent plutôt qu’à les entrer dans la queue pour plus tardM SK2
3. Accélération GPU OptionnelleM SK2
Pour un haut débit, utiliser DirectML (WindowsM SK2 ou CUDA:
dotnet add package Microsoft.ML.OnnxRuntime.DirectML # Windows GPU
# or
dotnet add package Microsoft.ML.OnnxRuntime.Gpu # NVIDIA CUDA
var options = new SessionOptions();
options.AppendExecutionProvider_DML(); // Use GPU
Quand utiliser cela contre LLM
| Utilisez ONNX NER | ||
|---|---|---|
| Volume élevé (1000s de dossiers) | ||
| Entités standards personnes, organismesM SK3 lieuxMSC4 | Types d'entités personnalisés | |
| PrivautéM SK1données sensibles | Lorsque vous avez besoin d'explications | |
| Pipelines déterministes | Analyse exploratoire | |
| Extraction des caractéristiques | Interprétation |
Les deux approches fonctionnent. Elles résolvent Différents problèmes. L'NER extrait la structure de ; La raison d'être des LLM quant au sens de .
Le plus grand tableau
Ce modèle-extraction déterministe avec des modèles congelés, suivie par une synthèse facultative plus tard-scales bien mieux que de pousser le texte brut dans un LLM et espérer qu’il se comporte
Le NER n’est pas quelque chose que vous "agentifiezM SK1 Il'l’infrastructure . Vous l’effectuez à l’ingestionMSC4 stockez les entitésMNK5 et les utilisez en aval pour le filtrageMRK6 la liaisonMMK7 ou l’alimentation dans des pipelines plus sophistiquésMEK8
La même approche s'applique aux autres tâches d'extraction des caractéristiques: embeddingsM SK1 classification , sentiment. Train une fois MSC4 ou utiliser préconiséMNK5 exportation à ONNXMRK6 courir partoutMST7 deterministementMst8
Lorsque cela convient: Ce OCR + Le pipeline de l’NER est un élément fondamentalM SK2 Pour le tableau complet- comment les entités extraites s’infiltrent dans la construction du graphe RAG réduit et le lucideRAG Documentation. Les entités que vous extraisez ici deviennent des nodes ; les documents deviennent des bords M SK2 le LLM ne voit qu'à ce qu'il a besoin .
Ressources
Bibliothèques & Modèles:
- Tesseract.NET - CM SK1 enveloppe pour Tesseract OCR
- Les données de l'essai sont les suivantes : - Dossiers de données formés pour Tesseract
- BERT-base-NER ONNX - Le modèle NER que nous utilisons
- Le temps de fonctionnement d'ONNX - Documentation officielle
- ML.Tokenizers - Bibliothèque de l'octroi du tokeniseur Microsoft'
Étape suivante:
- Partie 2: Le paquet NuGet - Ce pipeline en tant qu’un seul-line Installation NuGet avec le prétraitement OpenCVM SK2 Florence -2 visionMNK4 MicrosoftMRK5RecognizersMMK6 et un outil CLI
Articles connexes:
- Le pipeline Three-Tier OCR - Lorsque le simple OCR n’est pas suffisant
- RAG réduit - Lorsque les entités extraites correspondent à l’ensemble
- lucideRAG - Mise en œuvre complète avec déduplication des entités et construction de graphiques