فاز 5 · درس 3

پیاده‌سازی Word2Vec از صفر: بازنمایی معنایی کلمات با بردارها

نوع
ساختنی
زمان
حدود 75 دقیقه
زبان‌ها
Python
پیش‌نیاز
فاز 5 · درس 02 (BoW + TF-IDF)، فاز 3 · درس 03 (پیاده‌سازی پس‌انتشار خطا از صفر)
نمایی سه‌بعدی از نقطه‌های درخشان معلق در فضا که با بردارهای جهت‌دار به یکدیگر وصل شده‌اند
فهرست مطالب
  1. صورت مسئله
  2. مفهوم اصلی
  3. پیاده‌سازی از صفر
  4. گام 1: استخراج جفت‌های آموزشی از پیکره‌ی متنی
  5. گام 2: جدول‌های امبدینگ
  6. گام 3: تابع هدف نمونه‌برداری منفی
  7. گام 4: آموزش روی یک پیکره‌ی کوچک
  8. گام 5: ترفند تناسب و قیاس
  9. استفاده از ابزارهای واقعی
  10. کاربرد Word2Vec در سال 2026
  11. نقاط ضعف Word2Vec
  12. خروجی این درس
  13. تمرین‌ها
  14. اصطلاحات کلیدی
  15. برای مطالعه‌ی بیشتر

روش‌های سنتی مثل 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)، مسئله را به یک وظیفه‌ی دسته‌بندی دودویی تبدیل می‌کند: پیش‌بینی این‌که «آیا این کلمه‌ی بافت در مجاورت کلمه‌ی مرکزی آمده است، بله یا خیر؟». به این ترتیب، به‌جای محاسبه‌ی سافت‌مکس روی کل دایره‌ی واژگان، برای هر جفت داده‌ی آموزشی تنها تعداد انگشت‌شماری کلمه‌ی منفی (کلماتی که هم‌نشین نبوده‌اند) نمونه‌برداری می‌شوند.

حساب برداری کلماتبرای جمع بردارها بکشید
WORD VECTOR ARITHMETICWord2Vec arranges words so that relationships become directions. The vector from man to king is the same as woman to queen, so subtracting man and adding woman to king lands almost exactly on queen. Meaning becomes geometry, and analogy becomes vector addition.مردزنپادشاهملکه
مدل Word2Vec کلمات را به گونه‌ای می‌چیند که روابط معنایی تبدیل به جهت‌ها شوند. بردار مرد به پادشاه مشابه بردار زن به ملکه است؛ بنابراین کم کردن مرد و اضافه کردن زن به پادشاه، تقریباً دقیقاً روی ملکه می‌نشیند. معنا به هندسه و قیاس به جمع برداری تبدیل می‌شود.

پیاده‌سازی از صفر

گام 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.

تمرین‌ها

  1. ساده. چرخه‌ی آموزش را روی یک پیکره‌ی بسیار کوچک متنی (20 جمله درباره‌ی سگ‌ها و گربه‌ها) اجرا کنید. پس از 200 دور آموزش، با تابع nearest(vocab, W, W[vocab["cat"]]) مطمئن شوید که dog جزو 3 گزینه‌ی اول خروجی است. اگر نبود، تعداد دورها یا حجم واژگان را بیشتر کنید.
  2. متوسط. قابلیت زیرنمونه‌برداری (subsampling) را برای کلمات پرتکرار اضافه کنید؛ به طوری که کلمات با فراوانی بیش از 10^-5، با احتمالی متناسب با بسامدشان از جفت‌های آموزشی حذف شوند. تاثیر این ترفند را روی میزان شباهت واژه‌های نادر بسنجید.
  3. پیشرفته. مدلی روی مجموعه‌ی داده‌ی 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 نمی‌تواند برای آن برداری بسازد.

برای مطالعه‌ی بیشتر

خودآزمایی

به سؤال‌ها جواب بدهید تا ببینید چقدر از درس در ذهنتان مانده است.

  1. پیش از درسفرضیه توزیعی (distributional hypothesis) چیست؟
  2. پیش از درسچرا استفاده از سافت‌مکس معمولی روی واژگان در Word2Vec غیرعملی است؟
  3. حین درستفاوت بین skip-gram و CBOW چیست؟
  4. حین درسدر نمونه‌برداری منفی (negative sampling)، هدف برای جفت مثبت (مرکزی، بافت) چیست؟
  5. حین درسپس از آموزش Word2Vec، کدام ماتریس وزن به امبدینگ‌های کلمات تبدیل می‌شود؟
  6. پس از درسچرا Word2Vec روی کلمات چندمعنایی (مانند «شیر») شکست می‌خورد؟
  7. پس از درسدر سال 2026، در چه شرایطی ممکن است هنوز Word2Vec را به ترنسفورمر ترجیح دهید؟
  8. پس از درسچه چیزی قیاس معروف «king - man + woman ~ queen» را ممکن می‌سازد؟

سؤالات متداول

تفاوت مدل Skip-gram با CBOW در Word2Vec چیست؟

مدل Skip-gram کلمه‌ی مرکزی را می‌گیرد و کلمات پیرامونی را پیش‌بینی می‌کند که عملکرد بهتری روی کلمات نادر دارد، در حالی که CBOW کلمات اطراف را می‌گیرد تا کلمه‌ی مرکزی را حدس بزند.

چرا در Word2Vec از نمونه‌برداری منفی (Negative Sampling) استفاده می‌شود؟

محاسبه‌ی تابع سافت‌مکس روی تمام کلمات دایره‌ی واژگان (مثلاً 100 هزار کلمه) بسیار سنگین است. نمونه‌برداری منفی این مسئله را به یک دسته‌بندی دودویی سریع در برابر چند کلمه‌ی تصادفی تبدیل می‌کند.

بزرگ‌ترین محدودیت و نقطه‌ضعف Word2Vec چیست؟

این مدل به ازای هر کلمه تنها یک بردار ایستا دارد و توانایی تفکیک کلمات چندمعنایی بر اساس بافت جمله (مانند واژه‌ی «شیر» یا «bank») و همچنین کلمات خارج از دایره‌ی واژگان (OOV) را ندارد.

ما رو توی گوگل بیشتر ببین

با افزودن نکست ادمین به «منابع ترجیحی» گوگل، درس‌ها و مطالب ما را در بخش اخبار برتر جستجو بیشتر می‌بینید.