روشهای سنتی مثل Bag of Words یا TF-IDF کلمات را شناسههایی مجزا و بدون ارتباط معنایی میبینند. در چنین سیستمهایی، مدلی که مفهوم «سگ» را یاد گرفته هیچ درکی از واژهی «تولهسگ» ندارد و معنای متون جدید را بهدرستی دریافت نمیکند. حل این مشکل با فهرستکردن مترادفها هم در زبانهای تخصصی یا زبان فارسی با دامنهی واژگان غنی عملاً ناممکن است.
الگوریتم Word2Vec این مسئله را با نگاشت کلمات به یک فضای برداری پیوسته حل کرد؛ فضایی هندسی که در آن واژگان هممعنا در کنار هم مینشینند و روابط معنایی از طریق اعمال جبری ساده مانند تفریق و جمع برداری استخراج میشوند. یادگیری این ساختار پایه به شما کمک میکند سازوکار پنهان مدلهای زبانی امروزی را در سادهترین شکل خود بشناسید.
مزیت بزرگ پیادهسازی و اجرای مدلهای Word2Vec در این است که به هیچ سختافزار گرانقیمت یا سرویسهای ابری خارجی وابسته نیستند. شما میتوانید این مدلهای سبک را کاملاً محلی، روی CPU سیستم شخصی خود یا با کتابخانههایی مانند gensim آموزش دهید و در سامانههای واقعی به کار ببرید.
کلمه را از روی همنشینانش میشناسند. یک شبکهی کمعمق بر پایهی همین ایده آموزش دهید تا ساختار هندسی خودبهخود پدیدار شود.
صورت مسئله
روش TF-IDF میداند که dog و puppy دو کلمهی متفاوتاند، اما نمیفهمد معنایشان تقریباً یکی است. مدل دستهبندیکنندهای که روی dog آموزش دیده باشد، نمیتواند آموختههایش را به متنی دربارهی puppy تعمیم دهد. شاید بتوانید با تهیهی فهرستی از واژگان مترادف روی این نقص سرپوش بگذارید، اما این راهکار در مواجهه با واژههای نادر، اصطلاحات تخصصی و زبانهای پیشبینینشده عملاً با شکست مواجه میشود.
هدفتان دستیابی به نمایشی برداری است که در آن dog و puppy در فضای برداری کنار هم بنشینند؛ جایی که حاصل عبارت king - man + woman نزدیک به queen بیفتد و مدلی که روی dog آموزش دیده، بخشی از بار معنایی را رایگان به puppy منتقل کند.
مدل Word2Vec همین فضا را در اختیارمان گذاشت: یک شبکه عصبی دو لایه که روی تریلیونها توکن آموزش دید و در سال 2013 منتشر شد. معماری آن بهشدت و به شکلی غافلگیرکننده ساده است، اما نتایجش مسیر پردازش زبان طبیعی را تا یک دهه دگرگون کرد.
مفهوم اصلی
فرضیهی توزیعی (Distributional hypothesis؛ فرث، 1957): «کلمه را از روی همنشینانش میشناسند.» اگر دو کلمه در بافتها و سیاقهای متنی مشابهی ظاهر شوند، احتمالاً معنای مشابهی هم دارند.
الگوریتم Word2Vec با بهرهگیری از همین ایده، در دو قالب ارائه میشود:
- Skip-gram: دریافت کلمهی مرکزی و پیشبینی کلمات پیرامونی. به عنوان مثال با پنجرهای به طول 2:
cat -> (the, sat, on). - CBOW (مخفف continuous bag of words): دریافت کلمات پیرامونی و پیشبینی کلمهی مرکزی:
(the, sat, on) -> cat.
مدل Skip-gram در زمان آموزش کندتر است، اما کلمات نادر را بسیار بهتر مدیریت میکند؛ به همین دلیل به گزینهی پیشفرض تبدیل شد.
این شبکه فقط یک لایهی پنهان بدون تابع غیرخطی دارد. ورودی آن یک بردار وانهات (one-hot) به اندازهی طول واژگان (vocabulary) است و خروجی آن یک لایهی سافتمکس روی تمام واژگان. پس از پایان آموزش، لایهی خروجی دور انداخته میشود و وزنهای لایهی پنهان مستقیماً همان امبدینگها خواهند بود.
one-hot(center) ── W ──▶ hidden (d-dim) ── W' ──▶ softmax(vocab)
^
this is the embedding
اما ترفند اصلی کجاست؟ محاسبهی سافتمکس روی 100 هزار کلمه هزینهی پردازشی بسیار سنگینی دارد. الگوریتم Word2Vec با استفاده از نمونهبرداری منفی (negative sampling)، مسئله را به یک وظیفهی دستهبندی دودویی تبدیل میکند: پیشبینی اینکه «آیا این کلمهی بافت در مجاورت کلمهی مرکزی آمده است، بله یا خیر؟». به این ترتیب، بهجای محاسبهی سافتمکس روی کل دایرهی واژگان، برای هر جفت دادهی آموزشی تنها تعداد انگشتشماری کلمهی منفی (کلماتی که همنشین نبودهاند) نمونهبرداری میشوند.
پیادهسازی از صفر
گام 1: استخراج جفتهای آموزشی از پیکرهی متنی
def skipgram_pairs(docs, window=2):
pairs = []
for doc in docs:
for i, center in enumerate(doc):
for j in range(max(0, i - window), min(len(doc), i + window + 1)):
if i == j:
continue
pairs.append((center, doc[j]))
return pairs
>>> skipgram_pairs([["the", "cat", "sat", "on", "mat"]], window=2)
[('the', 'cat'), ('the', 'sat'),
('cat', 'the'), ('cat', 'sat'), ('cat', 'on'),
('sat', 'the'), ('sat', 'cat'), ('sat', 'on'), ('sat', 'mat'),
...]
هر جفت (کلمهی مرکزی، کلمهی بافت) درون پنجره، یک نمونهی آموزشی مثبت بهحساب میآید.
گام 2: جدولهای امبدینگ
به دو ماتریس نیاز داریم: W جدول امبدینگ کلمات مرکزی است (همانی که در پایان نگه میدارید) و 'W جدول کلمات بافت (که معمولاً دور ریخته میشود، یا گاهی با W میانگین گرفته میشود).
import numpy as np
def init_embeddings(vocab_size, dim, seed=0):
rng = np.random.default_rng(seed)
W = rng.normal(0, 0.1, size=(vocab_size, dim))
W_prime = rng.normal(0, 0.1, size=(vocab_size, dim))
return W, W_prime
وزنها را با مقادیر تصادفی کوچک مقداردهی اولیه میکنیم. در دنیای واقعی اندازهی واژگان 10k و بعد بردار 100 منطقی است؛ اما برای یادگیری، 50 واژه و بعد بردار 16 برای مشاهدهی هندسهی کار کفایت میکند.
گام 3: تابع هدف نمونهبرداری منفی
به ازای هر جفت مثبت (center, context)، تعداد k کلمهی تصادفی را به عنوان نمونههای منفی از دایرهی واژگان انتخاب میکنیم. سپس مدل را طوری آموزش میدهیم که ضرب داخلی W[center] · W'[context] برای جفتهای مثبت بالا و برای جفتهای منفی پایین باشد.
def sigmoid(x):
return 1.0 / (1.0 + np.exp(-np.clip(x, -20, 20)))
def train_pair(W, W_prime, center_idx, context_idx, negative_indices, lr):
v_c = W[center_idx]
u_pos = W_prime[context_idx]
u_negs = W_prime[negative_indices]
pos_score = sigmoid(v_c @ u_pos)
neg_scores = sigmoid(u_negs @ v_c)
grad_center = (pos_score - 1) * u_pos
for i, u in enumerate(u_negs):
grad_center += neg_scores[i] * u
W[context_idx] = W[context_idx]
W_prime[context_idx] -= lr * (pos_score - 1) * v_c
for i, neg_idx in enumerate(negative_indices):
W_prime[neg_idx] -= lr * neg_scores[i] * v_c
W[center_idx] -= lr * grad_center
فرمول جادویی کار به این صورت است: تابع زیان (loss) لاجستیک روی جفت مثبت (که میخواهیم خروجی سیگموید نزدیک به 1 باشد) بهعلاوهی تابع زیان لاجستیک روی جفتهای منفی (که میخواهیم خروجی سیگموید نزدیک به 0 باشد). گرادیان به هر دو جدول منتقل میشود. اثبات کامل روابط ریاضی در مقالهی اصلی آمده است؛ پیشنهاد میکنیم برای درک عمیقتر، یک بار آن را روی کاغذ حل کنید.
گام 4: آموزش روی یک پیکرهی کوچک
def train(docs, dim=16, window=2, k_neg=5, epochs=100, lr=0.05, seed=0):
vocab = build_vocab(docs)
vocab_size = len(vocab)
rng = np.random.default_rng(seed)
W, W_prime = init_embeddings(vocab_size, dim, seed=seed)
pairs = skipgram_pairs(docs, window=window)
for epoch in range(epochs):
rng.shuffle(pairs)
for center, context in pairs:
c_idx = vocab[center]
ctx_idx = vocab[context]
negs = rng.integers(0, vocab_size, size=k_neg)
negs = [n for n in negs if n != ctx_idx and n != c_idx]
train_pair(W, W_prime, c_idx, ctx_idx, negs, lr)
return vocab, W
پس از اجرای تعداد دورهای (epochs) کافی روی یک پیکرهی متنی بزرگ، کلماتی که بافتهای مشترک دارند امبدینگهای مرکزی بسیار نزدیکی پیدا میکنند. این اثر روی پیکرههای آزمایشی کوچک کمرنگ دیده میشود، اما در دادههایی با میلیاردها توکن فوقالعاده چشمگیر است.
گام 5: ترفند تناسب و قیاس
def nearest(vocab, W, target_vec, topk=5, exclude=None):
exclude = exclude or set()
inv_vocab = {i: w for w, i in vocab.items()}
norms = np.linalg.norm(W, axis=1, keepdims=True) + 1e-9
W_norm = W / norms
target = target_vec / (np.linalg.norm(target_vec) + 1e-9)
sims = W_norm @ target
order = np.argsort(-sims)
out = []
for i in order:
if i in exclude:
continue
out.append((inv_vocab[i], float(sims[i])))
if len(out) == topk:
break
return out
def analogy(vocab, W, a, b, c, topk=5):
v = W[vocab[b]] - W[vocab[a]] + W[vocab[c]]
return nearest(vocab, W, v, topk=topk, exclude={vocab[a], vocab[b], vocab[c]})
روی بردارهای 300 بعدی آموزشدیدهی Google News:
>>> analogy(vocab, W, "man", "king", "woman")
[('queen', 0.71), ('monarch', 0.62), ('princess', 0.59), ...]
حاصل king - man + woman = queen میشود؛ نه به این دلیل که مدل درکی از مفهوم سلطنت دارد، بلکه به این خاطر که بردار تفاضلی (king - man) مفهومی شبیه به «سلطنت» را ثبت کرده و افزودن آن به woman، نتیجه را در ناحیهی مربوط به زنان سلطنتی در فضای برداری قرار میدهد.
استفاده از ابزارهای واقعی
پیادهسازی Word2Vec از پایه صرفاً جنبهی آموزشی دارد؛ در محیط تولید واقعی از کتابخانهی gensim استفاده میشود.
from gensim.models import Word2Vec
sentences = [
["the", "cat", "sat", "on", "the", "mat"],
["the", "dog", "ran", "across", "the", "room"],
]
model = Word2Vec(
sentences,
vector_size=100,
window=5,
min_count=1,
sg=1,
negative=5,
workers=4,
epochs=30,
)
print(model.wv["cat"])
print(model.wv.most_similar("cat", topn=3))
در پروژههای واقعی تقریباً هیچوقت مدل Word2Vec را خودتان آموزش نمیدهید، بلکه بردارهای از پیش آموزشدیده را دانلود میکنید:
- GloVe: روش دانشگاه استنفورد بر پایهی تجزیهی ماتریس همآیی (co-occurrence matrix). دارای چکپوینتهای 50، 100، 200 و 300 بعدی با پوشش عمومی عالی. درس 04 بهطور خاص به بررسی GloVe اختصاص دارد.
- fastText: توسعهیافتهی Word2Vec توسط فیسبوک که برای n-gramهای کاراکتری امبدینگ تولید میکند. این مدل با ترکیب زیرکلمات (subwords) میتواند کلمات خارج از دایرهی واژگان را هم مدیریت کند (درس 04).
- بردارهای پیشآموزشدیدهی Word2Vec روی Google News: بردار 300 بعدی با واژگانی شامل 3 میلیون کلمه که سال 2013 منتشر شد و هنوز هم روزانه دانلود میشود.
کاربرد Word2Vec در سال 2026
- بازیابی سبک و سریع در دامنههای خاص: با یک ساعت آموزش روی چکیدهی مقالات پزشکی با لپتاپ معمولی، بردارهای تخصصی استخراج میکنید که هیچ مدل عمومی بزرگی توان تولیدش را ندارد.
- مهندسی ویژگی بر پایهی قیاس: با فرمول
gender_vector = mean(man - woman pairs)میتوانید محوری بسازید و با کم کردن آن از سایر کلمات، اثرهای جنسیتی را خنثی کنید؛ ترفندی که هنوز در پژوهشهای عدالت الگوریتمی (fairness) کاربرد دارد. - قابلیت تفسیرپذیری بالا: بعد 100 به اندازهای کوچک است که بتوان با روشهای PCA یا t-SNE خوشهها را رسم و تحلیل کرد.
- اجرا روی لبهی سختافزاری (Edge): هر جا نیاز باشد استنتاج بدون کارت گرافیک و روی دستگاه محلی اجرا شود؛ استخراج بردار در Word2Vec صرفاً خواندن یک سطر از جدول است.
نقاط ضعف Word2Vec
دیوار چندمعنایی (polysemy): کلمهی bank فقط یک بردار دارد؛ برداری که همزمان میان «ساحل رودخانه» و «مؤسسهی مالی» مشترک است. واژهی table (جدول آماری در برابر میز اداری) نیز وضعیتی مشابه دارد. مدلهای پاییندستی نمیتوانند معنای مدنظر را از روی این بردار واحد تشخیص دهند.
امبدینگهای وابسته به متن (مانند ELMo، BERT و تمام معماریهای ترنسفورمر پس از آن) این مشکل را حل کردند؛ یعنی بر اساس بافت و کلمات پیرامونی، بردار متفاوتی برای هر رخداد کلمه تولید میکنند. این همان جهش اساسی از Word2Vec به BERT است: گذر از امبدینگهای ایستا به امبدینگهای متنی. بخش 7 به سازوکار ترنسفورمرها میپردازد.
مشکل واژگان خارج از دایره (Out-of-vocabulary یا OOV) ضعف دیگر آن است. اگر عبارت Zoomer-approved در دادههای آموزشی نبوده باشد، Word2Vec هیچ راه جایگزینی ندارد و برداری برایش تولید نمیکند. این مشکل در مدل fastText با اتکا به ترکیب زیرکلمات برطرف شده است (درس 04).
خروجی این درس
کدها را در مسیر outputs/skill-embedding-probe.md ذخیره کنید:
---
name: embedding-probe
description: Inspect a word2vec model. Run analogies, find neighbors, diagnose quality.
version: 1.0.0
phase: 5
lesson: 03
tags: [nlp, embeddings, debugging]
---
You probe trained word embeddings to verify they are working. Given a `gensim.models.KeyedVectors` object and a vocabulary, you run:
1. Three canonical analogy tests. `king : man :: queen : woman`. `paris : france :: tokyo : japan`. `walking : walked :: swimming : ?`. Report the top-1 result and its cosine.
2. Five nearest-neighbor tests on domain-specific words the user supplies. Print top-5 neighbors with cosines.
3. One symmetry check. `similarity(a, b) == similarity(b, a)` to within float precision.
4. One degenerate check. If any embedding has a norm below 0.01 or above 100, the model has a training bug. Flag it.
Refuse to declare a model good on analogy accuracy alone. Analogy benchmarks are gameable and do not transfer to downstream tasks. Recommend intrinsic + downstream evaluation together.
تمرینها
- ساده. چرخهی آموزش را روی یک پیکرهی بسیار کوچک متنی (20 جمله دربارهی سگها و گربهها) اجرا کنید. پس از 200 دور آموزش، با تابع
nearest(vocab, W, W[vocab["cat"]])مطمئن شوید کهdogجزو 3 گزینهی اول خروجی است. اگر نبود، تعداد دورها یا حجم واژگان را بیشتر کنید. - متوسط. قابلیت زیرنمونهبرداری (subsampling) را برای کلمات پرتکرار اضافه کنید؛ به طوری که کلمات با فراوانی بیش از
10^-5، با احتمالی متناسب با بسامدشان از جفتهای آموزشی حذف شوند. تاثیر این ترفند را روی میزان شباهت واژههای نادر بسنجید. - پیشرفته. مدلی روی مجموعهی دادهی 20 Newsgroups آموزش دهید. سپس دو محور سوگیری تعریف کنید:
he - sheوdoctor - nurse. بردار عناوین شغلی مختلف را روی هر دو محور تصویر کنید و مشاغلی را که بیشترین شکاف سوگیری را دارند گزارش دهید. این روش دقیقاً همان ابزاری است که پژوهشگران عدالت هوش مصنوعی استفاده میکنند.
اصطلاحات کلیدی
| اصطلاح | در گفتار روزمره | معنای دقیق فنی |
|---|---|---|
| امبدینگ کلمه (Word embedding) | کلمه در قالب بردار | نمایشی متراکم و کمبعد (معمولاً بین 100 تا 300) که از روی بافت متنی یاد گرفته میشود. |
| Skip-gram | ترفند مدل Word2Vec | پیشبینی کلمات پیرامونی از روی کلمهی مرکزی؛ کندتر از CBOW، اما بسیار بهتر در کلمات نادر. |
| نمونهبرداری منفی (Negative sampling) | میانبُری در فرآیند آموزش | جایگزینی تابع سافتمکس روی کل واژگان با دستهبندی دودویی در برابر k کلمهی تصادفی. |
| امبدینگ ایستا (Static embedding) | یک بردار ثابت برای هر کلمه | برداری ثابت بدون توجه به بافت متن؛ ناتوان در برابر کلمات چندمعنایی. |
| امبدینگ وابسته به متن (Contextual embedding) | بردار حساس به سیاق متن | بردارهای متفاوت برای هر بار تکرار یک کلمه بسته به کلمات مجاور؛ چیزی که ترنسفورمرها تولید میکنند. |
| OOV | خارج از واژگان (Out of vocabulary) | واژهای که در دادههای آموزش غایب بوده است؛ Word2Vec نمیتواند برای آن برداری بسازد. |
برای مطالعهی بیشتر
- Mikolov et al. (2013). Distributed Representations of Words and Phrases and their Compositionality — مقالهی مرجع نمونهبرداری منفی؛ متنی کوتاه و بسیار خواندنی.
- Rong, X. (2014). word2vec Parameter Learning Explained — شفافترین اثبات ریاضی گرادیانهای مدل، اگر ریاضیات مقالهی اصلی به نظرتان فشرده و پیچیده است.
- gensim Word2Vec tutorial — راهنمای تنظیمات آموزشی در محیط تولید واقعی که واقعاً جواب میدهند.
