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
Enkel OCR och NER Feature Extraction i C# med ONNX
Wednesday, 21 January 2026
I ” ' ” har byggt klarRAG Jag läser sociala medier där folk frågar samma sak. djupt i OCR-rummet Jag tänkte att jag skulle skriva ett 'beginner friendly ' sätt att göra detta på icke-
Du har bilder med text.
Detta artikel visar så enkel som möjligt Pipeline: Tesseract för textuttvingning, så BERT NER ( via ONNX) för enhetens utmärkelse
Deterministisk betyder här fixera versioner , fixera språkdata, och inget adaptivt inlärning på starttid
NuGet kommer snart - I'm förpackar detta till en enkel
mostlylucid.ocrnerbibliotek. För tillfället
Den fulla Pipelinen
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
Två steg: ,, två modeller, ,, båda igång lokalt. ., Låt ' bygga varje del.
Del 1: OCR med Tesseract
Tesseract är standardopen, -, källkods OCR-motor. Tesseract.NET, en C
dotnet add package Tesseract
Du behöver också de tränade datafilerna. ladda ner eng.traineddata från tessdata och placera den i en tessdata folder.
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();
}
Det var "'", det var . ExtractText("invoice.png") och du får en string.
Nyckel:
TesseractEngineär dyrt att skapa
Begränsningar
Tesseract fungerar bra för ren, hög- kontrasttext i standardfonter. Den kämpar med
- Stiliserade eller dekorativa fonter
- Low-kvalitélla skanningar eller foton
- Roterade eller böjda text
- Text på komplexa bakgrunder
- Animaerade GIFs med undertitel
- Hyfenerade linjer bryter (
inter-\nnational) kan behöva processing efter - innan NER
För produktionssystem som behöver hantera konstiga saker Det tre---Tier-OCR-röret- som lägger till Florence -2 ONNX som en medelklass och Vision LLM uppgradering för hårda fall
För detta inlärning, antar vi att du har ren bild eller text från en annan källa.
I praktiken, vill du vanligtvis normalisera OCR-uttrycket ( trim whitespace , kollapsa upprepade nya linjer , fixa uppenbart hyphenation M SK4 innan du skickar ut det till NERM SK5
Del 2: NER med ONNX
Varför den här metoden fungerar
Innan vi ska dyka in i kod, låt oss förstå vad vi gör. Om du är en C-utvecklare som aldrig rört ML, så är det här för dig.
Vad är NER?
Namerade entitetserkänning (NER) är ett löst problem.
- PER - Personen namn "John Smith", "DrM SK4 Jane DoeMST5
- ORG - Organiseringar
- LOC - Lokalisering | " | London | ", | МSK3 | Mount Everest
- MISC - Andra entityder "COVID-19",
Modellet förstår inte texten, utan lär sig statistiska mönster från miljontals märkta exempel. NER är dragning av dragningar Det matchar mönster på steroider
Varför ONNX?
NX (, Open Neural Network Exchange, ) är ett standardformat för ML-modeller. frozen inference DLL för nervnätverk
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
Den viktigaste insikten någon annan gjorde hårt arbete (trening av modellen i Python ). Du kör bara slutsatsen i C
Varför inte bara använda en LLM
Du skulle kunna skicka ett sms till GPT, och fråga: "" Hitta människorna och företagen i det här smset.
| tillvägagångspunkt | Rymden | kostnaderna per | 1000 | dokument | integritet | konsistens | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Northeastern | ~50ms | ♫ ♫ $0 ♫ | ♫ Lokalt | ♫ Högt ♫ | |||||||||||
| Lokal LLM API | 4-30s | $0 ♫ ♫ | ♫ Små modeller kan vara fläckiga ♫ | ||||||||||||
| LLM API |
LLMs är bra för komplext resonemang. För mönster extraktion på skalaM SK1 en dedikerad modell är 40x snabbare och gratis .
Pipelinet
Här är vad vi bygger
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
Varje steg är enkelt.
steg 1: ladda ner modellen
Du behöver tre filer från HuggingFace. ladda ner dem manuell till en ordning (e .gM SK3 ./models/ner/):
| Att filera | Storleken | URL | ||
|---|---|---|---|---|
model.onnx |
~430MB | ladda ner | ||
vocab.txt |
~230KB | ladda ner | ||
config.json |
~1KB | ladda ner |
Modellet är bert-bas-NER exporterade till ONNX format av: skyddai.
Din folder borde se ut som:
models/
ner/
model.onnx (the neural network)
vocab.txt (word → ID mapping)
config.json (label definitions)
steg 2: Projektuppsättning
Att skapa en ny konsolapp och lägga till NuGet paket
dotnet new console -n NerDemo
cd NerDemo
dotnet add package Microsoft.ML.OnnxRuntime
dotnet add package Microsoft.ML.Tokenizers
Det är det. Två paket.
- OnnxRuntime - kör modellen
- ML.Tokenizer - Handler text → token omvandling
steg 3: förståndet av tokenisering
Innan modellen kan bearbeta texten, måste vi konvertera den till siffror. symbolisering.
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
Nyckelpunkter:
[CLS]och[SEP]är speciella token som markerar meningsgränser- Varje ord blir ett tal från
vocab.txt - Modellet ser bara siffror , aldrig den faktiska texten
Att ladda ut tokenisatorn
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);
Varför? LowerCaseBeforeTokenization = false? Den här modellen undervisades på kasserade texter . | | " | John · " | och " | john · M SK5 | har olika betydelser - | en | ' | är troligtvis ett namn
Nyckel: Markifieren måste matchar modellen exakt. Med hjälp av en annan vocab
vocab.txtsom skepper med modellen.
Att tokenisera text
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]");
Efter detta har vi
tokenIds:[101, 1287, 3455, 2573, 1120, 7513, 1999, 2414, 119, 102]tokens:["[CLS]", "John", "Smith", "works", "at", "Microsoft", "in", "London", ".", "[SEP]"]
steg 4: Att köra modellen
Nu lägger vi in dessa siffror i den ONNX-modellen.
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
Lade upp modellen
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);
Nyckel: Att skapa tokenisatorn och inferenssessionen en gång ♫ (singleton ♫
InferenceSessionär dyrt att skapa
Att förbereda inmatningar
modellen förväntar sig:
- input_ids: Våra tokensidentifikationer som
long[] - uppmärksamhet_ masker: 1 för riktiga tokens
// 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;
}
}
Running Inference
// 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>();
Utgången logits har form [1, sequence_length, 9]-9 möjliga etiketter för varje tokenposition
steg 5: Dekodering av utgången
Modellet ger ut råresultat. Vi behöver
- Hitta den högsta tärningsetikern för varje token
- Omvandla dessa etiketter till faktiska enheter
Understanding BIO Tags
Modellet använder BIO-notation:
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 = Början av en Person
- I-PER = Inuti ( Kontinuation | ) | en Person
- O = Utanför någon enhet ( inte intressant
- B-ORG = Början av en organisation
Detta låter modellen hantera flera ordbeståndsdelar som "John Smith
Etikettens kartläggning
// These are the 9 labels the model was trained on (CoNLL-2003 dataset)
string[] labels =
{
"O", // 0: Outside any entity
"B-PER", // 1: Beginning of Person
"I-PER", // 2: Inside Person
"B-ORG", // 3: Beginning of Organization
"I-ORG", // 4: Inside Organization
"B-LOC", // 5: Beginning of Location
"I-LOC", // 6: Inside Location
"B-MISC", // 7: Beginning of Miscellaneous
"I-MISC" // 8: Inside Miscellaneous
};
Beteckning: Den här specifika modellen använder standarden 9-label CoNLL-schemat
config.json(id2labelfält). Om du byter modeller, läsa etiketter från konfiguration snarare än att koda
Att hitta den bästa etiketten
För varje token, väljer vi etiketten med den högsta logit poängenM SK1
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));
}
Den Softmax funktionen konverterar rå poäng till sannolikheter (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;
}
Tillåtelse: Softmax poäng är relaterande, inte kalibrerade sannolikheter
0.92som "92% korrekt ". Använd dem för att filtrera låga ufdomsprognoser
steg 6: Extrahera enheter
Nu har vi per- -token förutsägelser.
Först, WordPiece sammanfogade hjälper. De här vingarna ## underord och punktspacing korrekt:
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();
}
Nu är enhetens extraktion minimalt märkeförtroende över hela skalan.
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;
}
Nota: WordPiece hanteras helt av MergeWordPieces- inga särskilda kontrollen behövs
Ett komplett exempel
Här är ett minimalt fungerande exempel som du kan kopiera och köra.
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-PER", "I-PER", "B-ORG", "I-ORG", "B-LOC", "I-LOC", "B-MISC", "I-MISC"];
// 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)}");
utgång:
Input: John Smith, CEO of Microsoft, announced the acquisition in London yesterday.
Entities found:
[PER] John Smith
[ORG] Microsoft
[LOC] London
Understanding WordPiece Subwords
BERT använder WordPiece-tokenisering, som delar ut okända ord i underord ## prefix betyder " Fortsättning av det tidigare ordet
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
Den MergeWordPieces hjälparbetare hanterar detta: ## symboler är tillskrivna utan ett utrymme, som producerar "Elasticsearch" istället för "Elastic search".
Performance-tips
1. Batch flera texter
Om du har många texter, bearbeta dem i lager.
// 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. Använd Smart Bucketing
Använd den minsta burken som passar
int[] buckets = [32, 64, 128, 256, 512];
int targetLength = buckets.FirstOrDefault(b => b >= tokenIds.Count);
if (targetLength == 0) targetLength = 512;
I praktiken, ONNX NER är tillräckligt snabb för att fungera iline under intag, inte bara som ett slagjobb. Du kan extrahera enheter när dokument kommer in snarare än att ställa upp dem i queue för senare .
3. GPU: s accelerering
För hög hastighet använder DirectML (Windows ) eller 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
När man ska använda detta mot LLM
| Använd ONNX NER | |||||||||
|---|---|---|---|---|---|---|---|---|---|
| Hög volymen | 1000 | Doser | 2 | 3 | 1 | 4 | analys | 5 | |
| Standardbeståndsdelar | ( | människor | , | organisationer | МSK3 | ställen | ) | ||
| Privatsphäre | |||||||||
| Deterministiska ledningar | Eksploratorisk analys | ||||||||
| Extraktion av egenskaper | Interpretering | / | Synthes |
Båda tillvägagångssätten fungerar olika problem. NER extraherar struktur
Den större bilden
Detta mönster-deterministisk extraktion med frysta modeller, följt av optionell syntektion senare- räknar mycket bättre än att trycka in råtext i en LLM och hoppas att den beter sig
NER är inte något du gör.
Samma tillvägagångssätt gäller även för andra dragningar av egenskaper.
Där det passar: Den här OCR: n | | +| NER: s ledning är en byggsten | Reduzerad RAG och LucidRAG-dokumentation. De varelser du extraherar här blir noder ; dokumenten blir rander ; LLM ser bara vad den behöver
resurser
Bibliotek & Modeller:
- Tesseract.NET - CM SK1 ompackare för Tesseract OCR
- tessdata - Tränade datafiler för Tesseract
- BERT-bas-NER ONNX - NER-modellen vi använder
- Runtime på ONNX - officiella dokumentation
- ML.Tokenizer - Microsofts tokeniseringsbibliotek
Related Articles:
- Det tre---Tier-OCR-röret - När enkel OCR inte är
- Reduzerad RAG - Där extraherade enheter passar in i större sammanhang
- LucidRAG - Full implementation with entity deduplication and graph construction