تبدیل متون خام به دادههای ساختاریافته یکی از چالشهای بنیادین در پردازش زبان طبیعی است. در هر سامانهای، از تحلیل رزومه و اسناد مالی گرفته تا ناشناسسازی پروندههای پزشکی و بهبود جستجو، استخراج دقیق اسامی افراد، سازمانها و مکانها گام نخست است. اگر این مرحله درست انجام نشود، کل خط پردازش بعدی دادهها مختل خواهد شد.
در این درس سیر تحول بازشناسی موجودیتهای نامدار (NER) را از روشهای سنتی مبتنی بر واژهنامه و الگوریتمهای آماری نظیر CRF بررسی میکنیم و به معماریهای عصبی BiLSTM-CRF، مدلهای ترنسفورمری BERT و تکنیکهای نوین با مدلهای زبانی بزرگ میرسیم تا بتوانید بهترین ابزار را برای سناریوی واقعی خود انتخاب کنید.
توجه داشته باشید که برای نصب پکیجهایی مانند Hugging Face، spaCy یا کتابخانههای کمکی پایتون از طریق PyPI، دانلود از داخل ایران گاهی با کندی یا اختلال مواجه میشود که با تنظیم میرورهای پایدار قابل رفع است. همچنین پیادهسازیهای پایهی این درس نیازی به پردازش ابری سنگین ندارند و بر روی سیستمهای شخصی معمولی نیز اجرا میشوند.
«اسمها را از متن بکش بیرون.» کار سادهای به نظر میرسد، تا وقتی که با مرزهای مبهم، موجودیتهای تودرتو و اصطلاحات تخصصی حوزه دستبهگریبان شوید.
صورت مسئله
«Apple sued Google over its iPhone search deal in the US.» در این جمله 5 موجودیت داریم: Apple از نوع ORG، Google از نوع ORG، iPhone از نوع PRODUCT، عبارت search deal (احتمالاً) و US از نوع GPE. یک سامانهی بازشناسی موجودیتهای نامدار (NER یا Named Entity Recognition) خوب همهی آنها را با نوع درست استخراج میکند. اما سامانهی ضعیف، iPhone را جا میاندازد، شرکت Apple را با میوهی سیب اشتباه میگیرد و برچسب PERSON به «US» میزند.
سامانهی NER اسب بارکش زیربنایی در تمام پایپلاینهای استخراج ساختاریافته است: تحلیل رزومهها، اسکن لاگهای انطباق با قوانین، ناشناسسازی پروندههای پزشکی، درک پرسوجوهای جستجو، متصلکردن پاسخهای چتبات به منابع مستند (Grounding) و استخراج داده از قراردادهای حقوقی. شاید خودش مستقیماً به چشم نیاید، اما همیشه به آن وابستهاید.
در این درس از مسیر سنتی و کلاسیک (قاعدهمحور، HMM، CRF) آغاز میکنیم و به روشهای مدرن (BiLSTM-CRF و سپس ترنسفورمرها) میرسیم. هر گام، محدودیت خاصی از گام قبلی را برطرف میکند؛ فهم همین سیر تکاملی، درس اصلی ماست.
مفهوم
برچسبگذاری BIO (یا BILOU) استخراج موجودیت را به یک مسئلهی برچسبگذاری توالی تبدیل میکند. در این روش، به هر توکن برچسب B-TYPE (آغاز موجودیت)، I-TYPE (درون موجودیت) یا O (خارج از هر موجودیتی) اختصاص داده میشود.
Apple B-ORG
sued O
Google B-ORG
over O
its O
iPhone B-PRODUCT
search O
deal O
in O
the O
US B-GPE
. O
موجودیتهای چندتوکنی به شکل زنجیرهای متصل میشوند: New B-GPE، York I-GPE، City I-GPE. مدلی که BIO را بفهمد، میتواند محدودههای متنی با هر طولی را استخراج کند.
سیر تکامل معماریها:
- قاعدهمحور (Rule-based): ترکیبی از عبارات باقاعده (Regex) و جستجو در واژهنامههای اسامی خاص (Gazetteer). دقت (Precision) بسیار بالایی روی موجودیتهای شناختهشده دارد، اما پوشش آن روی موارد جدید صفر است.
- مدل پنهان مارکوف (HMM): احتمال گسیل توکن بر اساس برچسب، و احتمال انتقال برچسب به برچسب دیگر. رمزگشایی با الگوریتم ویتربی انجام میشود و روی دادههای برچسبخورده آموزش میبیند.
- میدان تصادفی شرطی (CRF): شبیه HMM است اما رویکردی تمایزگرا (Discriminative) دارد، بنابراین میتوانید ویژگیهای دلخواه متعددی (شکل ظاهری کلمه، حروف بزرگ و کوچک، کلمات همسایه) را با هم ترکیب کنید. CRF در سال 2026 هنوز اسب بارکش کلاسیک برای استقرار در محیطهای با منابع پردازشی محدود به شمار میرود.
- مدل BiLSTM-CRF: ویژگیهای عصبی جایگزین ویژگیهای دستساز میشوند. لایهی LSTM جمله را در هر دو جهت میخواند و لایهی CRF روی آن، سازگاری توالی برچسبها را تضمین میکند.
- مبتنی بر ترنسفورمر: فاینتیون کردن BERT با یک سر دستهبندی توکن (Token-classification head). بالاترین صحت، ولی بیشترین مصرف منابع محاسباتی.
پیادهسازی از صفر
گام 1: توابع کمکی برای برچسبگذاری BIO
def spans_to_bio(tokens, spans):
labels = ["O"] * len(tokens)
for start, end, label in spans:
labels[start] = f"B-{label}"
for i in range(start + 1, end):
labels[i] = f"I-{label}"
return labels
def bio_to_spans(tokens, labels):
spans = []
current = None
for i, label in enumerate(labels):
if label.startswith("B-"):
if current:
spans.append(current)
current = (i, i + 1, label[2:])
elif label.startswith("I-") and current and current[2] == label[2:]:
current = (current[0], i + 1, current[2])
else:
if current:
spans.append(current)
current = None
if current:
spans.append(current)
return spans
>>> tokens = ["Apple", "sued", "Google", "over", "iPhone", "sales", "."]
>>> labels = ["B-ORG", "O", "B-ORG", "O", "B-PRODUCT", "O", "O"]
>>> bio_to_spans(tokens, labels)
[(0, 1, 'ORG'), (2, 3, 'ORG'), (4, 5, 'PRODUCT')]
گام 2: ویژگیهای دستساز
در NER کلاسیک (غیرعصبی)، همهچیز به مهندسی ویژگی وابسته است. نمونههایی از ویژگیهای مفید:
def token_features(token, prev_token, next_token):
return {
"lower": token.lower(),
"is_upper": token.isupper(),
"is_title": token.istitle(),
"has_digit": any(c.isdigit() for c in token),
"suffix_3": token[-3:].lower(),
"shape": word_shape(token),
"prev_lower": prev_token.lower() if prev_token else "<BOS>",
"next_lower": next_token.lower() if next_token else "<EOS>",
}
def word_shape(word):
out = []
for c in word:
if c.isupper():
out.append("X")
elif c.islower():
out.append("x")
elif c.isdigit():
out.append("d")
else:
out.append(c)
return "".join(out)
تابع word_shape("iPhone") خروجی xXxxxx میدهد و word_shape("USA-2024") مقدار XXX-dddd را برمیگرداند. الگوهای استفاده از حروف بزرگ و کوچک سیگنال بسیار قدرتمندی برای اسامی خاص هستند.
گام 3: یک مدل پایهی ساده بر اساس قواعد و واژهنامه
ORG_GAZETTEER = {"Apple", "Google", "Microsoft", "OpenAI", "Meta", "Amazon", "Netflix"}
GPE_GAZETTEER = {"US", "USA", "UK", "India", "Germany", "France"}
PRODUCT_GAZETTEER = {"iPhone", "Android", "Windows", "ChatGPT", "Claude"}
def rule_based_ner(tokens):
labels = []
for token in tokens:
if token in ORG_GAZETTEER:
labels.append("B-ORG")
elif token in GPE_GAZETTEER:
labels.append("B-GPE")
elif token in PRODUCT_GAZETTEER:
labels.append("B-PRODUCT")
else:
labels.append("O")
return labels
واژهنامههای اسامی خاص در محیطهای عملیاتی، میلیونها مدخل استخراجشده از Wikipedia و DBpedia دارند. پوشش آنها خوب است، اما در ابهامزدایی (مثلاً تشخیص شرکت Apple از میوهی سیب) بسیار ضعیف عمل میکنند. به همین دلیل مدلهای آماری برنده شدند.
گام 4: گام CRF (طرح کلی، نه پیادهسازی کامل)
پیادهسازی کامل CRF از صفر در 50 خط کد، بدون پرداختن به مبانی نظریهی احتمال چندان آموزنده نیست. به جای آن از sklearn-crfsuite استفاده کنید:
import sklearn_crfsuite
def to_features(tokens):
out = []
for i, tok in enumerate(tokens):
prev = tokens[i - 1] if i > 0 else ""
nxt = tokens[i + 1] if i + 1 < len(tokens) else ""
out.append({
"word.lower()": tok.lower(),
"word.isupper()": tok.isupper(),
"word.istitle()": tok.istitle(),
"word.isdigit()": tok.isdigit(),
"word.suffix3": tok[-3:].lower(),
"word.shape": word_shape(tok),
"prev.word.lower()": prev.lower(),
"next.word.lower()": nxt.lower(),
"BOS": i == 0,
"EOS": i == len(tokens) - 1,
})
return out
crf = sklearn_crfsuite.CRF(algorithm="lbfgs", c1=0.1, c2=0.1, max_iterations=100, all_possible_transitions=True)
X_train = [to_features(s) for s in sentences_tokenized]
crf.fit(X_train, bio_labels_train)
پارامترهای c1 و c2 ضرایب تنظیمکنندهی L1 و L2 هستند. با تعیین all_possible_transitions=True، مدل یاد میگیرد توالیهای غیرمجاز (مانند آمدن I-ORG بعد از O) نامحتمل هستند؛ به این ترتیب CRF بدون نیاز به کدنویسی دستیِ محدودیتها، سازگاری قالب BIO را حفظ میکند.
گام 5: ارزش افزودهی BiLSTM-CRF
در این مدل، ویژگیها یاد گرفته میشوند. ورودیها امبدینگهای توکن (مثل GloVe یا fastText) هستند. شبکه LSTM متن را از چپ به راست و راست به چپ میخواند. سپس وضعیتهای پنهان متصلشده وارد یک لایهی خروجی CRF میشوند. لایهی CRF همچنان سازگاری توالی برچسبها را حفظ میکند؛ LSTM هم ویژگیهای دستساز را با ویژگیهای یادگرفتهشده جایگزین میسازد.
import torch
import torch.nn as nn
class BiLSTM_CRF_Head(nn.Module):
def __init__(self, vocab_size, embed_dim, hidden_dim, n_labels):
super().__init__()
self.embed = nn.Embedding(vocab_size, embed_dim)
self.lstm = nn.LSTM(embed_dim, hidden_dim, bidirectional=True, batch_first=True)
self.fc = nn.Linear(hidden_dim * 2, n_labels)
def forward(self, token_ids):
e = self.embed(token_ids)
h, _ = self.lstm(e)
emissions = self.fc(h)
return emissions
برای لایهی CRF میتوانید از پکیج torchcrf.CRF (نصب با دستور pip install pytorch-crf) استفاده کنید. بهبود حاصل از این روش نسبت به CRF با ویژگیهای دستی محسوس است، اما اگر دهها هزار جملهی برچسبخورده نداشته باشید، این تفاوت کمتر از حد انتظارتان خواهد بود.
استفاده از ابزارهای واقعی
کتابخانهی spaCy مدلهای NER آمادهی تولید را در اختیارتان میگذارد:
import spacy
nlp = spacy.load("en_core_web_sm")
doc = nlp("Apple sued Google over its iPhone search deal in the US.")
for ent in doc.ents:
print(f"{ent.text:20s} {ent.label_}")
Apple ORG
Google ORG
iPhone ORG
US GPE
دقت کنید که iPhone به جای PRODUCT برچسب ORG خورده است؛ مدل کوچک spaCy در پوشش موجودیتهای حوزهی محصولات ضعف دارد. مدل بزرگتر (en_core_web_lg) عملکرد بهتری دارد و مدل ترنسفورمری (en_core_web_trf) حتی از آن هم بهتر عمل میکند.
استفاده از Hugging Face برای NER بر پایهی BERT:
from transformers import pipeline
ner = pipeline("ner", model="dslim/bert-base-NER", aggregation_strategy="simple")
print(ner("Apple sued Google over its iPhone in the US."))
[{'entity_group': 'ORG', 'word': 'Apple', ...},
{'entity_group': 'ORG', 'word': 'Google', ...},
{'entity_group': 'MISC', 'word': 'iPhone', ...},
{'entity_group': 'LOC', 'word': 'US', ...}]
گزینهی aggregation_strategy="simple" توکنهای متوالی B-X و I-X را در یک محدوده ادغام (merge) میکند. بدون این گزینه، خروجی در سطح تکتک توکنها خواهد بود و باید کار merge را خودتان انجام دهید.
استخراج موجودیت با مدلهای زبانی بزرگ (گزینهی سال 2026)
روشهای Zero-shot و Few-shot با مدلهای زبانی بزرگ امروزه در بسیاری از حوزهها با مدلهای فاینتیونشده رقابت میکنند و زمانی که دادههای برچسبخورده کمیاب باشند، عملکرد بهمراتب بهتری نشان میدهند.
- پرامپتنویسی Zero-shot: فهرستی از انواع موجودیتها همراه با نمونهای از ساختار خروجی به مدل بدهید و خروجی را در قالب JSON بخواهید. این روش بدون هیچ تنظیمی کار میکند، هرچند دقت آن در حوزههای کاملاً جدید متوسط است.
- پرامپتنویسی به سبک ZeroTuneBio: کار را به چند مرحله بشکنید: استخراج کاندیداها ← توضیح معنایی ← قضاوت نهایی ← بازبینی مجدد. یک پرامپت چندمرحلهای (به جای یکمرحلهای) دقت بازشناسی موجودیتهای زیستپزشکی را به شکل چشمگیری افزایش میدهد. همین الگو برای حوزههای حقوقی، مالی و متون علمی نیز بسیار کارآمد است.
- پرامپتنویسی پویا با RAG: برای هر فراخوانی استنتاج، مشابهترین نمونههای برچسبخورده را از یک مجموعهی کوچک نشانهگذاریشده بازیابی کنید و پرامپت Few-shot را در لحظه بسازید. در بنچمارکهای سال 2026، این تکنیک امتیاز F1 مدل GPT-4 را در NER حوزهی پزشکی بین 11 تا 12 درصد نسبت به پرامپتهای ایستا بالا میبرد.
- تفکیک بر اساس نوع موجودیت: در متنهای طولانی، استخراج همزمان همهی موجودیتها با یک فراخوانی باعث افت بازخوانی (Recall) با افزایش طول متن میشود. کار درست این است که به ازای هر نوع موجودیت، یک دور استخراج جداگانه اجرا کنید. این کار هزینهی استنتاج را بالا میبرد، اما دقت را بهشدت افزایش میدهد و الگوی استاندارد پردازش گزارشهای بالینی و قراردادهای حقوقی است.
توصیهی عملیاتی برای سال 2026: قبل از جمعآوری دادههای آموزشی، کار را با یک مدل پایهی Zero-shot بر مبنای مدلهای زبانی بزرگ شروع کنید. در بیشتر مواقع نمرهی F1 حاصل بهقدری خوب است که اصلاً نیازی به فاینتیون نخواهید داشت.
جاهایی که NER کلاسیک هنوز برنده است
با وجود در دسترس بودن مدلهای زبانی بزرگ، روشهای سنتی NER در این شرایط انتخاب بهتری هستند:
- بودجهی تأخیر زمانی (Latency) کمتر از 50 میلیثانیه باشد.
- هزاران نمونهی برچسبخورده در اختیار دارید و به F1 بالاتر از 98 درصد نیاز دارید.
- دامنهی کاری هستانشناسی پایداری دارد که مدلهای ازپیشآموزشدیدهی CRF یا BiLSTM بهخوبی روی آن جواب میدهند.
- الزامات قانونی سازمان، استفاده از یک مدل زایشی (Generative model) را ممنوع کرده و پردازش داخلی (On-prem) را اجباری میکند.
نقاط ضعف و چالشها
- تغییر حوزه (Domain shift): مدلی که روی دادههای عمومی CoNLL آموزش دیده، روی متون حقوقی عملکردی بدتر از یک واژهنامهی ساده نشان میدهد. مدل را حتماً روی دادههای حوزهی خودتان فاینتیون کنید.
- موجودیتهای تودرتو (Nested entities): عبارت «Bank of America Tower» بهطور همزمان هم یک سازمان (ORG) و هم یک مکان فیزیکی (FACILITY) است. قالب استاندارد BIO توانایی نمایش محدودههای همپوشان را ندارد. برای این کار به NER تودرتو (الگوهای چندمرحلهای یا مدلهای مبتنی بر Span) نیاز دارید.
- موجودیتهای طولانی: عبارتی مثل «United States Federal Deposit Insurance Corporation». مدلهای مبتنی بر سطح توکن گاهی این عبارتها را تکهتکه میکنند. از
aggregation_strategyیا مراحل پسپردازش استفاده کنید. - انواع کمیاب موجودیتها: برچسبهای تخصصی پزشکی مانند DRUG_BRAND، ADVERSE_EVENT و DOSE. مدلهای عمومی شناختی از این موارد ندارند. برای شروع کار در این حوزهها باید سراغ ابزارهایی مانند Scispacy و BioBERT بروید.
خروجی این درس
این محتوا را در مسیر outputs/skill-ner-picker.md ذخیره کنید:
---
name: ner-picker
description: Pick the right NER approach for a given extraction task.
version: 1.0.0
phase: 5
lesson: 06
tags: [nlp, ner, extraction]
---
Given a task description (domain, label set, language, latency, data volume), output:
1. Approach. Rule-based + gazetteer, CRF, BiLSTM-CRF, or transformer fine-tune.
2. Starting model. Name it (spaCy model ID, Hugging Face checkpoint ID, or "custom, trained from scratch").
3. Labeling strategy. BIO, BILOU, or span-based. Justify in one sentence.
4. Evaluation. Use `seqeval`. Always report entity-level F1 (not token-level).
Refuse to recommend fine-tuning a transformer for under 500 labeled examples unless the user already has a pretrained domain model. Flag nested entities as needing span-based or multi-pass models. Require a gazetteer audit if the user mentions "production scale" and labels are unchanged from CoNLL-2003.
تمرینها
- ساده. تابع
bio_to_spans(عکس عملکردspans_to_bio) را پیادهسازی کنید و درستیِ رفتوبرگشت آن را روی 10 جمله بیازمایید. - متوسط. مدل CRF با
sklearn-crfsuiteکه بالاتر معرفی شد را روی مجموعهدادهی انگلیسی CoNLL-2003 آموزش دهید. گزارش F1 را به تفکیک موجودیتها با استفاده ازseqevalبه دست آورید. خروجی معمول در حدود F1 معادل 84 است. - سخت. مدل
distilbert-base-casedرا روی یک مجموعهدادهی NER تخصصی (پزشکی، حقوقی یا مالی) فاینتیون کنید. نتیجه را با مدل کوچک spaCy مقایسه کنید. بررسیهای مربوط به نشت داده (Data Leakage) را مستند کرده و نکات غافلگیرکنندهای که با آنها روبهرو شدید را بنویسید.
اصطلاحات کلیدی
| اصطلاح | در بیان عمومی | معنای دقیق فنی |
|---|---|---|
| NER | بیرون کشیدن اسامی | برچسبزدن محدودههای توکن با نوع موجودیت (PERSON، ORG، GPE، DATE و...). |
| BIO | طرح برچسبگذاری | B-X نشانگر آغاز، I-X نشانگر ادامه و O یعنی خارج از موجودیت. |
| BILOU | نسخهی بهتر BIO | افزودن برچسبهای L-X (آخرین توکن) و U-X (تکتوکنی) برای دقت بالاتر در تعیین مرزها. |
| CRF | دستهبند ساختاریافته | مدلی که انتقال بین برچسبها را مدلسازی میکند، نه فقط گسیل آنها را؛ در نتیجه توالیهای نامعتبر را حذف میکند. |
| Nested NER | موجودیتهای همپوشان | حالتی که یک محدودهی بزرگتر، خودش نوع دیگری از موجودیت را در دل دارد. فرمت BIO توان نمایش آن را ندارد. |
| F1 در سطح موجودیت | معیار ارزیابی درست NER | محدودهی پیشبینیشده باید دقیقاً با محدودهی واقعی منطبق باشد. ارزیابی F1 در سطح توکن، میزان دقت را بیش از حد واقعی نشان میدهد. |
برای مطالعهی بیشتر
- Lample et al. (2016). Neural Architectures for Named Entity Recognition — مقالهی مبنایی و کلاسیک دربارهی معماری BiLSTM-CRF.
- Devlin et al. (2018). BERT: Pre-training of Deep Bidirectional Transformers — ارائهدهندهی الگوی دستهبندی توکنها که بعدها به استاندارد صنعت تبدیل شد.
- ویژگیهای زبانی spaCy — موجودیتهای نامدار — مرجع کاربردی برای تمام ویژگیهای موجود روی
Doc.entsوSpan. - seqeval — کتابخانهی استاندارد و درست برای محاسبهی معیارهای ارزیابی. همیشه از همین ابزار استفاده کنید.
