# C#与ONNX的简单OCR和净化地物采掘

<!-- category -- AI,OCR,NER,ONNX,Docker,CSharp,Tutorial -->
<datetime class="hidden">2026-01-21T12:00</datetime>

随着I'的建设 [***清晰*RAG 区域包**](https://www.lucidrag.com) I'm阅读社交媒体, 人们不断询问同样的事情@. @ @ '你如何从扫描文本中获取功能? @?'}分类错误总是{'}只要使用LLM'...}它有用但非常昂贵的#.SO 就像I'一样 [在 OCR 空间深处](/blog/constrained-fuzzy-image-ocr-pipeline) 我想I'd写一个“'Beginner friend's 'M”的方法,

您有文本的图像@ .}您想要提取文字\ ,} 然后在其中找到有用的结构 * @ MS K2_ names @ MPK3} company\ , place*\ I} 未拨打 LLM=,}将数据传送到云端@ MASK7}或支付每个象征性的款项\.}

本条显示: **尽可能简单** 输油管: **宇宙魔方** 文本提取@, @% 然后 **净净额** @(_Via ONNX)}所有确定性{.}全部在C=#.中♪

这里的决定因素是指固定版本@,固定语言数据 @,,在运行时没有适应性学习_.

> **NuGet 马上就到** - I'm 将此包装为简单 `mostlylucid.ocrner` 下面的代码是复制@-_paste 准备 @.}

[TOC]

---


## 完整管道

```mermaid
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
```

双步,两步 ,两种模式都运行本地端"MS K2" Let\'"每个部分.

---


# 带有宇宙魔方的 & M1: OCR 部件

[宇宙魔方](https://github.com/tesseract-ocr/tesseract) 是标准打开@-}来源于 OCR 引擎\ .}We'}使用 [宇宙魔方=.NET](https://github.com/charlesw/tesseract)-=YTET -伊甸园字幕组=- 翻译:

```bash
dotnet add package Tesseract
```

您也需要经过培训的数据文件@.下载 `eng.traineddata` 从 [矩阵数据](https://github.com/tesseract-ocr/tessdata) 并把它放在 `tessdata` .\ {}

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

呼唤 `ExtractText("invoice.png")` 并获得一个字符串“.”

> **重要**: `TesseractEngine` 创建@. @ 在真实应用程序中 <% 1 xB1\bord1\shad1\3cH2F2F4F}创建此软件非常昂贵, 并且重新使用它+. _BAR_

### 限制

宇宙魔方运作良好 **clean ,高-contrast 标准字体中的文字**和:搏斗

- 立体或装饰字体
- 低@-}质量扫描或照片
- 旋转或曲线的文字
- 复杂背景的文字
- 带有字幕的动画性GIF
- 连字符换行@ ( @%`inter-\nnational`) 可能需要在 NER 前加处理

对于需要处理怪事的生产系统来说 , [3-Tier OCR管道](/blog/constrained-fuzzy-image-ocr-pipeline)加上Florence-2ONNX,

此教程 @ , @ we'}将假设您有来自其它源的干净图像或文字@ MS K2_ PDF 解析@ MPK3} 抄件 @ I- @ pasteQ, @ secl+.).}

> 在实际操作中,您通常会想要将 OCR 输出正常化 : @ (trim whitespace@ MS K2 折叠重复的新线 =, 修补明显的连字符@) 然后再将其传递给 NERQ.}

---


# 与ONNX的 NER 部件

## 为何本方法工作

在潜入代码前 Let'}让我们理解我们实际上在做什么 '}如果您是C\'°Bre a CQ#_BARBAR_开发者 谁从未碰过 MLQ,}本节是为您准备的

### 什么是NER ??

**命名实体识别@(NER})** 研究者训练了神经网络,可以阅读文字并突出显示“"”和“有趣的位子”

- **百分比** -=YTET -伊甸园字幕组=- 翻译:
- **其他资源** -=YTET -伊甸园字幕组=- 翻译:
- **LOC 业务发生点** “- 位置”“(" 伦敦 ", " 珠穆朗玛峰”
- **MISC** -=YTET -伊甸园字幕组=- 翻译:

该模型从上百万个标注的例子中学习了统计模式 . **NER 是特性提取@, @ 而不是推理@ .** 它在类固醇上的匹配模式 .

### 为什么是ONX ?

**ONNX** ( 开放神经网络交换 *) 是 ML 模型的标准格式*. 想象一下 **冻结的推断值 DLL** 对于神经网络来说 :固定重量, 在 @,@ shallors out, 没有训练逻辑\,没有随机=:

```mermaid
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“).”中训练模型。

### 为何不只使用 LLM?

您可以发送文本到 GPT-4, 并询问“ "” 在文本中找到人和公司@".它工作}#! But @:

@|Q 接近@|Q 速度 @MS K2Q 成本/每兆克3Q docs *|Q 隐私=|Q 一致性= |Q#
|-------------------|-------|--------------|-----------------------|-------------|
| **净当量** @| @ ~50 @% ms@ MS K2} @ I$0 @ @ O| @ 本地 @ NSK5# 高中 @ MPK6 @ @
| **当地LLM API** 小型模型可以是片状的 @|变数 @MS K6
| **LLM API** “|”“1-5”“|”“I$20-50” “MS K4”数据从外部发送的“|”变量“MSC6”数据。

LLMs非常适合复杂的推理@. 用于比例尺的图案提取}, 一个专门的模型是#40x

---


## 管道

这里:'+#我们正在建造的 '+_BARBAR_

```mermaid
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
```

每一步都是简单的... .... Let''...

---


## 步骤 @1:_ 下载模型

您需要三个文件, 从 Huggging Face@ . @ 手动下载到文件夹“ MS K1 @ e @ . @ g.,” 。 `./models/ner/`):

|QFile @|Q大小@|QURL#|
|------|------|-----|
| `model.onnx` | MS K1MB| [下载下载](https://huggingface.co/protectai/bert-base-NER-onnx/resolve/main/model.onnx) |
| `vocab.txt` | MS K1KB| [下载下载](https://huggingface.co/protectai/bert-base-NER-onnx/resolve/main/vocab.txt) |
| `config.json` | MS K1KB| [下载下载](https://huggingface.co/protectai/bert-base-NER-onnx/resolve/main/config.json) |

模式是 [{\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}Birt@-_base @-NER {\fn方正准圆简体\fs12\1cHC9ECC4}翻译:](https://huggingface.co/dslim/bert-base-NER) 导出到 ONNX 格式 [保护组织](https://huggingface.co/protectai/bert-base-NER-onnx).

您的文件夹应该看起来像@ : @

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

---


## 步骤 @2:工程设置

创建一个新的控制台应用程序并添加 NuGet 软件包@: @% 1

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

两个包裹 :

- **Onnx 运行时** - 运行模型
- **ML . 收缩器** - handles text *% 1 → @ signal transfer

---


## 步骤 @3:_ 理解招数

在模型处理文本前 , 我们需要将其转换为数字@.}这叫做 **符号表示式**.

```mermaid
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`
- 模型只看到数字 @,}从不见实际文本

### 正在装入调制器

```csharp
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`@?_@ MSKO} 这个模型是用例数文字来训练的 : @ MS K1 @ @ @ I" @ joñ" @ 和" @john @ MPK5 @ 有不同的含义 : s=-one @ MOK7 @ 可能有一个名字@, @ one_ ' @ 很可能不是. @ @

> **重要**: 代碼器 *必须* 使用不同的 vocab,}外壳选项@ ,}{或特殊代号ID 会静悄悄地降低结果=.} 总是使用 `vocab.txt` 使用模型的船舶 *.*

### 缩进文本

```csharp
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]"]`

---


## 运行模型

现在,我们把这些数字输入 ONNX 模型@.该模型返回“" @logits @"-}每个位置的每一个可能标签的分数”\.

```mermaid
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
```

### 正在装入模型

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

> **重要**: 创建质记器和推文会话, 一次@( @singleton @/Service_)}并重新使用它们 *.}唐'}不按文件重新加载*-}`InferenceSession` 创建@ . @ action

### 准备投入

模型期待#:

- **输入@ _ids**: 我们的代号为 `long[]`
- **注意:_mask**@ : @ @ I1 @ 真实的牌子@ MS K2 @ * 0} 挂贴

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

### 运行中的推断

```csharp
// 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_ 每一个象征性位置的可能标签@.}

---


## 正在解码输出

模型输出原始分数.}我们需要到:

1. 为每个标记寻找最高@ - @ scrowring 标签
2. 将这些标签转换为实际实体

### 了解 BIO 标记

模型使用 **BIO 记号**:

```mermaid
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
```

- **马斯克・波德** 个人实体的开始
- **伊姆斯克诺皮尔** @= @Q(@csurvation_) 个人实体
- **OO** @=#在任何实体外 @(}不有趣 )
- **宾斯克罗格** 组织开始

这让模型能够处理多@ - @word 实体, 如 @ MS K1_ John Smith% "} 或 @ MPK3_ United Kingdom @ I".}

### 标签映射

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

> **注注注注注释注注说明附注注注,注注 注注的注 注 注 注注说明注注附注注 注的注注 注注说注注的附注注说明,注 注,注说明的注说明说明注 注说明注说明 注注 注附注注的注释注 注 注 注注释注说明注释注的说明注,说明注的 注 注说明说明说明 注 注的说明说明,说明说明的附注 注注附注说明注附注的注附注 注 注注释说明注注释的注注释 注注注释注释注,的注, 注注,注释注附注附注注注释说明的说明 注说明 注的注释 注 注附注说明说明注释说明说明附注 注说明的注释注释注释说明 注注释注释 注说明注释注释的说明注释 注注释 注的附注注释注注释,注注释附注注附注注释的 注注脚注注注**:_BARBAR_使用标准的“9- label CoNLL schema.%ONOX”出口中包括标签名称。 `config.json` (`id2label` 字段@). @% 如果您要交换模型 @ , @ 读取配置而非硬编码的标签 @ MPK2}

### 寻找最佳标签

每个标牌的“,” 我们选择最高登录分的标签 “:”

```csharp
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):

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

> **关于信任的说明**微软分数是 *相对数*@ ,_ 尚未校准概率@ MS K1} 它们='\ 有用于排序和阈值@ I,, 但 dono{ ' t treat `0.92` 使用它们过滤低端的-信任预测 *,* 不是作为地面真相=.}

---


## 采掘实体

现在,我们有了 - -token预测... .......

最初的, WordPiece 合并助手@.这些控件 `##` 子词和标点间距正确@ : @

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

现在实体 提取.}实体信任就是 **最小象征性信任度** 在横跨“-”和“保守”、“,”、“因此,一个微弱的标牌是‘'’ 以平均%:’来隐藏吗?

```csharp
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`不需要特殊控制流 *.*

---


## 完整示例

这里“ ' @ ” 是您可以复制和运行的最小工作示例@ MS K1\ {

```csharp
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 子词

BERT 用途 **WordPiece 符号化**, 将未知的单词分割成子字. `##` 前缀意指 @ "_ 继续前一个单词@ MS K1\ {}

```mermaid
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` 帮助手处理此@ : @ {} `##` 添加了没有空格的自定义符@, production `"Elasticsearch"` 代替 `"Elastic search"`.

---


## 绩效提示

### 1.批次多文本

如果您有很多文本, 请在批量中处理@ , @ title: group

```csharp
// 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. 使用聪明的下巴

使用最小的桶子 适合: *

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

在实际操作中 ,ONNX NER足够快可以运行 **摄食期间的内线**@, 不只是批量工作@. 您可以在文件到达时提取实体, 而不是排队等待 #.#

### -=YTET -伊甸园字幕组=- 翻译:

对于高排气量 ,使用直接ML *(Windows})或CUDA:

```bash
dotnet add package Microsoft.ML.OnnxRuntime.DirectML  # Windows GPU
# or
dotnet add package Microsoft.ML.OnnxRuntime.Gpu       # NVIDIA CUDA
```

```csharp
var options = new SessionOptions();
options.AppendExecutionProvider_DML();  // Use GPU
```

---


## 何时使用此 vs LLM

|使用ONNX NER|使用LLMMS K2GPT-4/Claude}|
|--------------|------------------------|
-=YTET -伊甸园字幕组=- 翻译:
|+标准实体 @(+People_,+Ongs=,#places@)+#|#海关实体类型 @MS K6}
当您需要解释时 |
| 确定性管道 MS K1 探索分析
| 功能提取@| 解释 @/ 合成 @MSC3

两种方法都能解决 **不同问题**.NER抽取结构;LMs 解释意涵的原因吗?.

---


## 大图片

此图案@ - @%**含有冷冻模型的确定性抽取,随后可选合成**@-_ 比例远胜于将原始文字推入LLM, 希望它能表现好一点#.}

NER 不是你做的“"”或“I,”或 “输入更精密的管道”

同一方法也适用于其他地物提取任务@:嵌入,分类 @,asume_. train 一旦“(or”使用预先训练的\), 将出口到 ONNX, 跑遍各地 @,确定性MSQK8}

> **何处适合**此 OCR +NER输油管是一块建筑块_.。 对于完整图象 <-how professional depactive > ,dexter},和检索>-se [减少的RAG](/blog/reduced-rag-concept) 和 [LucidRAG文件](https://github.com/scottgal/lucidrag)“. ” 您在此提取的实体成为节点“ ; ” 文件变成边缘“ IMS K2 ” , LLM只看到它需要什么“ MSSK3 ”

---


## 资源资源资源 资源和资源资源资源

**圖書館&模擬**:

- **[宇宙魔方=.NET](https://github.com/charlesw/tesseract)** 用于宇宙魔方 OCR 的 C#包装器
- **[矩阵数据](https://github.com/tesseract-ocr/tessdata)** \ -\ {} 为宇宙魔方培训的数据文件
- **[BERT-Base-NEN ONNX(星际迷航)](https://huggingface.co/protectai/bert-base-NER-onnx)** - 我们使用的NER模型
- **[NONX 运行时间](https://onnxruntime.ai/docs/)** * 正式文件
- **[ML . 收缩器](https://www.nuget.org/packages/Microsoft.ML.Tokenizers)** - 微软{'}%s massizer 库

**相关条款**:

- **[3-Tier OCR管道](/blog/constrained-fuzzy-image-ocr-pipeline)** 当简单的 OCR 足够简单时 '
- **[减少的RAG](/blog/reduced-rag-concept)** “- 采掘实体适合大局的地方”
- **[卢西德拉格](https://github.com/scottgal/lucidrag)** *- 全面实施实体重复和图形构建