استخراج خصائص الـ OCR و NER بسيطة في C# مع ONNX (العربية (Arabic))

استخراج خصائص الـ OCR و NER بسيطة في C# مع ONNX

Wednesday, 21 January 2026

//

17 minute read

كما كان I' يبني وضوحRAG أنا أقرأ وسائل الإعلام الإجتماعية حيث يسأل الناس نفس الشيء. عميقاً في مساحة الـ OCR اعتقدت انني سوف اكتب طريقة ' ملائمة للمبتدئين ' للطريقة غير الملائمة لتلك الطريقة

لديك صور مع نص . تريد استخراج ذلك النص , ثم تجد البنية المفيدة في داخله | ( | الأسماء |, | الشركات | МSK4 | الأماكن | ) | دون أن تتصل بـ LLM

هذه المقالة تظهر أبسط ما يمكن خط أنابيب: تيسيراكت من أجل استخراج النص, عندها بيرت نيور (via ONNX) للتعرف على الكياناتM SK2 جميعها محلية . جميعهم محددين . جميعهما في CMSC5

تعبر هنا عن نسخ ثابتة, بيانات لغة ثابتة

غويت قادم قريباً - I'm أقوم بتغليف هذا إلى شيء بسيط mostlylucid.ocrner مكتبة. لحد الآن, الشفرة التالية هي النسخ -paste readyM SK3


الأنبوب الكامل

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

خطوات اثنان , نماذج اثنتين , كلاهما يعمل محلياً | . | دع |' | يبني كل جزء |


أجزاء 1: أو سي آر مع تيسيراكت

تيسيراكت هو نموذج مفتوح - source OCR engine . سنستخدم Tesseract.NET, مُغلّم C# مَغلِم .

dotnet add package Tesseract

تحتاج أيضاً إلى حزم البيانات المدرّسة. تحميل eng.traineddata من بيانات تيس ووضعها في 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();
}

تلك هي .. المكالمة ExtractText("invoice.png") وتحصل على سلسلة

مهم: TesseractEngine يكلف صنعه. في التطبيقات الحقيقية, صنعها مرة واحدة وإعادة استخدامها

الحدود

تسيراكت تعمل بشكل جيد نظيف, عالي-contrast text in standard fonts. إنها تعاني مع:

  • خطوط مصممة أو مرسومة
  • منخفضة -دقة المسح أو الصور
  • النص المدور أو المنحني
  • النص على خلفيات معقدة
  • الرسوم المتحركة GIFs مع الترجمات
  • كسرات خطية مكتوبة (inter-\nnational) قد يحتاج إلى معالجة بعد - قبل NER

للأنظمة الإنتاجية التي تحتاج إلى التعامل مع الأشياء الغريبة , أنظر أنابيب تير ثلاثية -- الذي يضيف فلورانس-2 ONNX كطبقة متوسطة وتسليط لرؤية LLM للمواد الصلبة .

لهذا الدروس , نحن' سنفترض أن لديكم صور أو نص نظيفة من مصدر آخر ( تحليل PDF

في الواقع , سترغب عادة في توصيل خروجية الـOCR ( إحداث فضاء أبيض | , إنحدار خطوط جديدة مكررة |, إصلاح المصطلحات الواضحة ٬) قبل أن تنقلها إلى NER


جزء 2: NER مع ONNX

لماذا هذا النهج ينجح

قبل أن نغوص في البرمجة، دعونا نفهم ما نفعله بالفعل.

ما هو NER?

التعرف على الكائنات المسمى (NER) هو مشكلة حلة. لقد قام الباحثون بتدريب شبكات عصبية يمكنها قراءة النص وتسليط الضوء على "الجزئيات المثيرة

  • PER MSC0 أسماء الأشخاص MSC1 جون سميث MSc2 msc3 د.مsc4 جين دو MScs5
  • ORG - منظمات (" مايكروسوفت", "NHSM SK4 |" |Acme Corp | ")
  • LOC - المواقع ("لوندن", "شارع إيفريستM SK4
  • MISC - كائنات أخرى ("COVID-19", "iPhone 15")

النموذج لا يفهم 't " understood" the textM SK3itMSC4s learned statistical patterns from millions of labelled examples NER هو استخراج الخصائص, ليس استدلالًا. إنها تطابق النمط على الاستيدروجين

لماذا ONNX?

أونكس (تبادل شبكات عصبية مفتوحة) هو النموذج القياسي لنماذج الميل . frozen inference DLL لشبكات عصبية: ثقوب ثابتة في, مقبضات خارجيّةM SK2 لا منطق تدريبيّ

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). تقوم فقط بتشغيل الاستنتاج في CM SK2

لماذا لا نستخدم فقط LLM?

يمكنك إرسال رسالة نصية إلى GPT-4 وتسأل "إيجاد الأشخاص والشركات في هذا النصM SK2 إنه ينجح

المقاربة السرعة التكلفة لكل 1000 دوتات خصوصيية مSK5 التماسك مSK6
أونكس نيور ~50ms $0
واجهة LLM محلية 4-30s $0 | النماذج الصغيرة يمكنها أن تكون رقيقة متغير
LLM API

LLMs great for complex reasoning. For pattern extraction at scaleM SK1 a dedicated model is 40x faster and freeMSC3


الأنبوب

هذا ما نصنعه هنا

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. تحمّلهم لاسيًا إلى папة M SK1e .g., ./models/ner/):

الملف الحجم URL
model.onnx ~430MB تنزيل
vocab.txt مسك0 ~230KB تنزيل
config.json مسك0 ~1KB تنزيل

النموذج هو bert-base-NER 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

ذلك ' s it . بطاقتين :

  • OnnxRuntime - يشغل النموذج
  • ML.توكينر - يدوي النص → تحويل الرموز

الخطوة 3: فهم التوجيه

قبل أن يستطيع النموذج معالجة النص , نحتاج إلى تحويله إلى أرقام . هذا يدعى التدوين.

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? تم تدريب هذا النماذج على نص مكعب. "JohnM SK3 و MSC4johnMSC5 لها معناه مختلفةMST6oneMS'محتمل أن يكون اسمًا

مهم: الرمز يجب أن تتطابق بالنموذج بالضبط. بإستخدام حرف مختلف, إختيار القضبان , أو هوية رموز خاصة سوف تحطم النتائج بصمت 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

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

مهم: إنشاء session of tokenizer and inference once (singletonInferenceSession من التكلفة أن تصنع

إعداد المدخلات

النموذج يتوقع:

  • الدخل_ids: إختبارات نصيبنا كـ long[]
  • الملاحظة_الغطاء: 1 للرموز الحقيقية, |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 possibly labels for each token position.


الخطوة 5: فك شفرة المخرج

النموذج ينتج نقاط خامة. نحن بحاجة إلى

  1. أبحث عن أعلى علامة تقييم - لكل رموز
  2. تحويل هذه العلامات إلى كائنات حقيقية

فهم العلامات 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 = بداية كيان الشخص
  • I-PER = داخل (استمرار) للكيان الشخصي
  • أو = خارج أي كيان ( لا مثيرة للاهتمام
  • B-ORG = بداية منظمة

هذا يسمح للنموذج بتعامل مع العديد من -كلمات مثل "جون سميث" أو "مملكة المتحدةM 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-label schema CoNLL. بعض निर्यातات ONNX تتضمن أسماء العلامات في config.json (id2label مجال). إذا قمت باستبدال النماذج, قراءة العلامات من konfigur بدلاً عن البرمجة القاسيةM SK2

إيجاد أفضل علامة تجارية

لكل علامة نختار العلامة ذات التقييم الأعلى

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 Function converts raw scores to probabilities (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 هي النسيج, احتمالات غير مقيَّدة. هي ' مفيدة لتصنيف وتقدير الدرجاتM SK3 لكنها لا تعالج 0.92 كـ "92% صحيحة". استخدمها لتصفية توقعات الثقة المنخفضة -M SK3 وليست حقاً أرضياً


الخطوة 6: استخراج Entities

الآن لدينا توقعات للـ - ,token ,. .علينا أن ندمجها في كيانات ..

أولا, مساعدات دمج 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();
}

الآن استخراج الكائنات . الثقة الكائنة هي الثقة المحدودة في الرموز على امتداد المسافة - conservative , بحيث لا يتم إخفاء علامة ضعيفة واحدة .

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

ال sortie:

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

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

فهم كلمات فرعية WordPiece

يستخدم BERT تدوين WordPiece, الذي يقسم الكلمات غير المعروفة إلى كلمات فرعية ## prefix means "continuation of previous word":

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. أجزاء متعددة من النص

إذا كان لديك الكثير من الرسائل , تعالجها في مجموعات :

// 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. إستخدام مشبك ذكي

لا تقلصوا دائماً إلى 512. استخدموا أصغر سطل يلائم :

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

في الواقع , ONNX NER سريع بما فيه الكفاية لتشغيله في الخطوط أثناء الإمتصاص, ليس فقط كمهمة عمل. يمكنك استخراج الكائنات عند وصول الوثائق بدلاً من الاصطفاء لها في وقت لاحق

3. تسارع GPU ( إختياري)

للسرعة العالية , إستخدم 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 إستخدام LLM (GPTM SK3Claude)
حجم كبير (1000s من الأبحاث ) واحد -تحليل خارجي
الوحدات القياسية ( الناس, المنظماتM SK3 الأماكنMNK4 أنواع الوحدت المخصصة SNK6
خصوصية-بيانات حساسة عندما تحتاج إلى تفسير
خطوط أنابيب إختبارية تحليل استكشافي
استخراج الخصائص تفسير / استنساخ

كلا النهجين يعملون. يحلون مشاكل مختلفة. يستخرج NER الهيكل; سبب LLM عن المعنىM SK2


الصورة الأكبر

هذا النمط-الاستخراج الإفتراضي مع نماذج متجمدة, تتبعها التركيبة البديلة لاحقاً- يتقلص أفضل بكثير من دفع النص الخام إلى LLM ويأمل أن يتصرف

NER ليس شيئًا تقوم به "agentify". إنهM SK2بنية تحتية . تقوم بتشغيله عند الإدخالMSC4 تخزين الكائناتMNK5 وتستخدمها في الأسفل لصفيتهاMRK6 ربطهاMMK7 أو توصيلها في خطوط أنابيب أكثر تعقيدًا

نفس المقاربة تطبق على مهمات إستخراج الخصائص الأخرى..

أين يلائم هذا: هذا الرسم البياني + خط أنابيب النطاق الشمالي هو قطعة بناء واحدة. للصورة الكاملةM SK3 كيف تتغذى الكائنات المستخرجة في بناء الرسوم البيانيةMSC4 التكرارMNK5 والإستخراجMMK6انظر تقلل من RAG و وثائق LucidRAG. الكائنات التي تخرجها هنا تصبح العقدة; الوثائق تصبح الحوافM SK2 يشاهد LLM فقط ما يحتاجه


الموارد

المكتبات & نماذج:

المقالات المرتبطة:

  • أنابيب تير ثلاثية - - عندما يكون الرسم البياني البسيط غير كاف
  • تقلل من RAG - أين تلائم الكائنات المستخرجة الصورة الأكبر
  • LucidRAG - تنفيذ كامل مع تفكيك الوحدات وبناء الرسوم البيانية
Finding related posts...
logo

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