Back to "Прості OCR і NER Funkції Extraction в C# з ONNX"

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 CSharp Docker NER OCR ONNX Tutorial

Прості OCR і NER Funkції Extraction в C# з ONNX

Wednesday, 21 January 2026

Так, як я будую прозораРАГ Я читаю в соціальних медіа, де люди питають одне й те саме. МСК1 МСК2 Як ви отримуєте характеристики від сканованого тексту? МСК3 помилка категорії завжди. ' просто користуйтесь LLM '... яка працює, але дуже дорого. глибоко в просторі OCR Я подумав, що я напишу 'напрочуд дружній для początkujących ' підхід до NON

У вас є зображення з текстом. Ви хочете виокремити цей текстM SK1 тоді знаходити корисний структуру всередині нього ( назви , компаніїMska4 місцяMска5 без виклику LLMM Ska6 відправлення даних до хмариMSKA7 або платити за один токенМska8

Ця стаття показує найпростіше трубка: Тесеракт для видобутку тексту, тоді БЕРТ NER (via ONNXM SK1 для розпізнавання об 'єктів . Все локальне МSK3 Все детерміністичне M SK4 Все в C #.

Детерміністично тут означає фіксовані версії, фіксоване значення мовиM SK1 і відсутність адаптивного навчання в часі запуску

NuGet скоро прийде - IM SK1m упакуємо це в простий mostlylucid.ocrner бібліотека. На даний момент


Цілковита труба

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

Два кроки, два моделі , обидва локально працюютьM SK2 Дозвольте' збудувати кожну частинуMSC4


Частина 1: ОCR з Tesseract

Тесеракт це стандартний відкритий -ソース OCR двигун . Ми МSK2 використаємо Теsseract.NET, упаковка CM SK1

dotnet add package Tesseract

Вам також потрібні треновані файли даних. завантажити eng.traineddata від тести і помістити його в tessdata папка.

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();
}

Це - МСК0, це - МSK1 ExtractText("invoice.png") і ви отримуєте струну.

Важливо: TesseractEngine це дорого створити. В реальному програмуванніМSK1 створити його один раз і відновити

Ограничення

Теsseract працює добре для чистий, високийM SK1 контрастний текст в стандартних шрифтах. Він бореться з

  • Стилізовані чи декоративні шрифти
  • Низка якість сканів або фотографій
  • Розвернутий або вигнутий текст
  • Текст на складних фонах
  • Анімовані GIF з субтитрами
  • Похибні лінії (inter-\nnational) може потребувати обробки післяM SK1 перед NER

Для систем виробництва, які мають справу з дивними речами, см. Трійна труба МСК0Tier-, що додає Флоренції -2 ONNX як середній рівень і ескалацію Vision LLM для жорстких корпусів

Для цього уроку ми припускаємо, що ви маєте чисті зображення або текст з іншого джерела.

У практиці, ви зазвичай хочете нормалізувати OCR вихідні коди ( trim whitespace, collapse repeated newlinesM SK3 fix obvious hyphenationMska4 before passing it to NERM Ska5


Частина 2: NER з ONNX

Чому цей підхід працює?

Перед тим, як зануритися у код, треба зрозуміти, що ми насправді робимо. Якщо ви - програміст, який ніколи не торкався ML,

Що таке NER?

Визначання наименованих об 'єктів (NERM SK1 є розв 'язаною проблемою. Дослідники навчили нейронні мережі, які можуть читати текст і виділяти цікаві біти

  • PER - Назви людей МSK1Джон Сміт МSK2 " Доктор мSK4 Джейн Доe "
  • ORG - Організації МSK1Microsoft", "NHSM SK4 | | 5 | Acme Corp | МSK6 |
  • LOC - Местоположення
  • MISC - Інші об 'єкти МSK1COVIDM SK2 МSK3iPhone 15"

Модель не розуміє тексту NER - це виділення властивостей, не обґрунтуванняM SK1 Імовірність співпадіння шаблону на стероидах

Чому ONNX?

NX ( Open Neural Network Exchange ) це стандартний формат для моделей ML МSK2 Подумайте про це як про заморожена деференція DLL для нейронних мереж: фіксовані вага вM SK1 витяжники назовні, без тренувальної логіки МSK3 без випадковості :

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

Найголовніша думка: хтось інший зробив важку роботу (викладання моделі в PythonіM SK1 Ви просто виконуєте висновки у C#.

Чому б не просто використовувати LLM?

Ви можете надіслати текст на GPT-4 і запитати: M SK1знайомте людей та компанії в цьому текстіMSC2 Це працює! АлеMST4

Погляньмо на це. МSK1 Швидкість.
NER МSK0 ~50ms МSK2 ♫ ♫ $0 ♫
Місцевий LLM API МSK0 4-30s МSK2 $0 \ МSK4 Малі моделі можуть бути склоподібними
LLM API МSK0 1-5s МSK2 $20-50 \ МSK4 Дані надсилаються назовні Варіабель M

ЛЛМ чудові для складного обґрунтування. Для видобутку шаблонів в масштабіM SK1 спеціальною моделью є 40x швидше і вільніше.


Трубопровод

Ось що ми будуємо

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

Кожен крок простий.


Шаг 1: завантажити модель

Вам потрібні три файли з HuggingFace. завантажити їх ręczно до папки M SK1e .g., ./models/ner/):

Файлі МSK1 Размер МSK2 यूआरएल
model.onnx МSK0 ~430MB МSK2 Загрузити
vocab.txt МSK0 ~230KB МSK2 Загрузити
config.json МSK0 ~1KB МSK2 Загрузити

Модель bert-baseM SK1NER exported to ONNX format by захист.

Ваша папка має виглядати як:

models/
  ner/
    model.onnx      (the neural network)
    vocab.txt       (word → ID mapping)
    config.json     (label definitions)

Шаг 2: Установка проекту

Створити нову консольну програму і додати пакети NuGet:

dotnet new console -n NerDemo
cd NerDemo
dotnet add package Microsoft.ML.OnnxRuntime
dotnet add package Microsoft.ML.Tokenizers

Це – МСК0, це – МSK1, два пакети, МSK2

  • Час trwania Onnx - запускає модель
  • ML.Tokenizers - Рухає текстом МSK1 конвертація टोकена

Шаг 3: Усвідомлення символізації

Перед тим, як модель зможе обробляти текст, ми повинні конвертувати його в цифриM SK1 Це називається символізація.

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

Ключевые моменти:

  • [CLS] і [SEP] є особливими символами, які позначають межі речення
  • Кожне слово стає числом від vocab.txt
  • Модель бачить тільки числа, ніколи не справжній текст

Завантаження токенізера

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);

Чому? LowerCaseBeforeTokenization = false? Ця модель була навчена на корпусному тексті . МSK2Джон МSK3 і СМСК4Джун ММСК5 мають різні значення СМСК6 один МПСК7 скоріш за все ім 'я

Важливо: Токенізер повинна точно співпадуть з моделлю. Використовуючи інший vocabM SK1 опцію обробки , або індивідуальні значки символів тиші погіршують результати vocab.txt що судна з моделью.

Це символізує текст

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]");

Після цього ми маємо

  • tokenIds: [101, 1287, 3455, 2573, 1120, 7513, 1999, 2414, 119, 102]
  • tokens: ["[CLS]", "John", "Smith", "works", "at", "Microsoft", "in", "London", ".", "[SEP]"]

Шаг 4: Запуск моделі

Тепер ми додаємо ці цифри до моделі ONNX. Модель повертає "логітиM SK2raw результати для кожного можливого ярлика на кожному місціMSC3

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

Завантаження моделі

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);

Важливо: Створити токенізер і сеанс висновків один раз.InferenceSession дуже дорого створити.

Подготування вхідних даних

Модель очікує:

  • вхід_іди: Наші торгові марки long[]
  • увагу_маскаМSK0 1 для реальних токенівM SK2 \0 для підкладки
// 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;
    }
}

Рухна інференція

// 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>();

Виход logits має форму [1, sequence_length, 9]-9 можливі ярлики для кожного місця символуM SK1


Шаг 5: Декодування вихідного коду

Модель виводить сирові очки. Нам потрібноM SK1

  1. Найти найвищий - ярлик оцінки для кожного символу
  2. Перетворити ці ярлики на справжні речі

Усвідомлення BIO Tags

Модель використовує 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 = Початок Person entity
  • I-PER МSK0 Всередині (продовжуванняM SK2 особи
  • О = За межами будь-якого об 'єкта
  • B-ORG = Початок організації

Це дозволяє моделлю працювати з багатьма-іменними об 'єктами, такими як M SK1Джон Сміт " або МSK3Великобританія МSK4

Мапування ярликів

// 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
};

Примітка: Це специфічна модель, яка використовує стандартну 9-схему CoNLL зображень config.json (id2label поле). Якщо ви поміняєте моделіM SK1 читайте ярлики з конфігурації, а не за допомогою кодування

Найти найкращий ярлик

Для кожного символу ми вибираємо ярлик з найвищим logit балом:

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));
}

І Softmax функція перетворює чисті партитури на ймовірність (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;
}

Довідка про довіру: Оцінки Softmax родичі, не калібровані вірогідності . Вони МSK2 є корисними для рейтингу та порогового значення M SK3 але їх не лікують 0.92 як "92% правильнийM SK1 Використовуйте їх для того, щоб відфільтрувати низькі - прогнози впевненості МSK3 не як базову правду


Шаг 6: Витягування об 'єктів

Тепер ми маємо прогнози на% 1,% 2,% 3,% 4,% 5. Нам потрібно об 'єднати їх в один ціль.

Перші, допоміжні дозволяти поєднати WordPiece. Ці ручки ## слова-заголовки та відмітки правильно:

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();
}

Тепер виділення об 'єкту. Довіра об' єкту мінімальний рівень довіру через весь діапазон, так що один слабкий символ не приховується середнім показником

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;
}

Примітка: WordPiece повністю обробляється MergeWordPieces-не потрібно спеціального управління потоком


Повний приклад

Тут' є мінімальним прикладом, який ви можете скопіювати і запустити

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)}");

Виход:

Input: John Smith, CEO of Microsoft, announced the acquisition in London yesterday.

Entities found:
  [PER] John Smith
  [ORG] Microsoft
  [LOC] London

Усвідомлення підслов WordPiece

БЕРТ використовує Токенізація WordPiece,, що розділяє невідомі слова на підслова ## попереднє слово означає "продовжування попереднього словаM SK1

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

І MergeWordPieces допомічник керує цим: ## टोकони прикріплені без простору,, що виробляє "Elasticsearch" замість "Elastic search".


Наполегшення розвитку

1. Множество текстів в пакеті

Якщо ви маєте багато текстів, обробляйте їх в партіяхM SK1

// 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. Використовуйте розумний букетинг

' не завжди прикріплятись до МSK1 Використовуйте найменший контейнер, який вписується в :

int[] buckets = [32, 64, 128, 256, 512];
int targetLength = buckets.FirstOrDefault(b => b >= tokenIds.Count);
if (targetLength == 0) targetLength = 512;

На практиці, ONNX NER досить швидко працює встромлений під час поглинання, не просто як пакетна роботаM SK1 Ви можете витягати сутності, коли приходять документи, а не переміщувати їх в чергах на пізніше.

3. ГПЗ Швидшення OptionalM SK2

Для високої пропускної спроможності, використовуйте DirectML (WindowsM SK2 або 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

Коли використовувати це проти LLM

Використовуйте ONNX NER
високий об 'єм МSK1 шл. доків МSK2 один мSK4 аналіз ззовні M
Стандартні об 'єкти МSK1 люди МSK2 організації , місця
конфіденциальністьM SK1чутливі дані Коли вам потрібні пояснения МSK3
Детерміністичні труби МSK1 Дослідковий аналіз МSK2
Видобуток властивостей МSK1 інтерпретація МSK2 синтез

Обидві методи працюють. Вони розв 'язують різні проблеми. NER виділяє структуру ; LLM - причину значущості МSK2


Велика картина

Ця модель-детерміністичне видобуток з замороженими моделями,, потім опціональна синтеза- набагато краще, ніж втиснути чистий текст у LLM і сподіватися, що він буде вестись так

NER - це не те, що ви робите.

Те ж саме стосується і інших завдань по видобутку властивостей.

Де це підходить?: Це OCR + NER труба є одним з будівельних блоків МSK2 Для повної картини МSK3 як виокремлені об 'єкти входять у графову конструкцію Reduced RAG і Документація LucidRAG. Втіли, які ви тут витягуєте, стають вузлими ; документи стають крапочками МSK2 LLM бачить тільки те, що йому потрібно


Ресурс

Кітапники & Моделі:

Подібні статті:

  • Трійна труба МСК0Tier - Коли простий ОCR недостатньо
  • Reduced RAG - Де виділені об 'єкти вписуються у загальну картину
  • LucidRAG - Полне введення з дедукцією об 'єктів та конструюванням графів
logo

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