Вбудоване слово представляє слово з коротким вектором вивчених чисел. Word2vec є корисним способом вивчення того, як ці числа набувають структури: його навчальне завдання досить просте, щоб переходити від контекстного вікна до оновлення градієнта.

Відома аналогія king − man + woman ≈ queen ілюструє можливий векторний зв’язок. На діаграмі нижче використовуються вручну розміщені 2D-точки, а не виміряні вектори word2vec. Його рівні зсуви є точними за конструкцією; реальні результати аналогії є приблизними і залежать від корпусу, моделі та методу порівняння.

Натисніть кнопку крок, щоб спостерігати за покроковим будівництвом.

Schematic: hand-placed 2D points illustrate vector arithmetic.

mankingwomanv(man), v(king)v(woman)

У навченій моделі координати надходять із оптимізації, а не з іменованих семантичних осей. Деякі зв’язки можуть виглядати як корисні вказівки, але немає універсальної «роялті» координати чи зміщення, яке б працювало для кожного слова.

Представлення One-hot і TF-IDF надають кожному елементу словника окрему координату. Натомість щільні вбудовування використовують спільний, менший набір розмірів. Обидва залишаються корисними представленнями; вбудовування додають навчений спосіб порівнювати слова за межами точної ідентичності.

Word2vec, представлений у 2013 році, зробив широкомасштабне навчання векторів слів ефективнішим за допомогою поверхневих моделей прогнозування. Він базувався на попередніх розподілених представленнях, а не вводив ідею вивчених векторів слів.

Skip-gram передбачає контекстні слова з центрального слова; CBOW передбачає центральне слово з його контексту. Слова з подібним використанням надають схожі докази передбачення, що може призвести до пов’язаних уявлень. Це ефект статистичного навчання, а не гарантія того, що кожне семантичне відношення стане чистим векторним зсувом.

Від гіпотези до геометрії

Гіпотеза розподілу пов’язує подібні контексти з пов’язаними значеннями або граматичними ролями. Це корисний доказ, а не повне визначення значення: такі антоніми, як hot і cold, також можуть мати багато спільного контексту.

Невеликий розмір вбудовування обмежує матрицю показників моделі мати низький ранг, заохочуючи спільну структуру прогнозування. Він не прив’язує слова до однакових координат. Насправді заміна E на E A і E' на A⁻¹ E' для оборотної матриці A зберігає кожну оцінку скалярного добутку, водночас потенційно змінюючи відстані та кути між введеннями вхідних даних.

Це допомагає розрізняти три варіанти використання «вбудовування»:

Статичні вбудовування слів призначають слову той самий вектор у різних контекстах. Контекстуальні вбудовування, наприклад приховані стани BERT, залежать від навколишніх токенів. Вбудовані речення або уривки підсумовують довший текст для порівняння чи пошуку, часто з додатковим контрастним навчанням. Ці уявлення не обов’язково мають однакову навчальну мету.

Авторегресійний трансформатор також починається з пошуку вбудованих токенів, як правило, поверх токенів підслів. Його пізніші приховані стани залежать від попереднього контексту. Об’єднання цих станів автоматично не створює хорошого вбудовування пошуку; це окремий модельний вибір.

У цій статті розглядаються вбудовування статичних слів від підрахунку сумісних появ до skip-gram і CBOW, а потім пояснюється, що змінюється, коли представлення стають контекстними.

Гіпотеза розподілу

У далекому 1957 році лінгвіст Дж. Р. Ферт написав знамениту фразу: «Ви впізнаєте слово по компанії, яку воно веде». Ось і вся ідея. Слова, які зустрічаються в схожих контекстах, мають подібні значення. Щоб зрозуміти чому, розгляньте три речення, в яких пропущено одне слово:

  • ___ гавкнув на листоношу.
  • ___ мурчав у мене на колінах.
  • ___ полетів зимувати на південь.

Вам не потрібно знати пропущені слова, щоб знати, що вони стосуються різних видів тварин. Контекст — слова навколо пропуску — звужує те, що підходить. Це одна половина ідеї: контекст передбачає слово.

Переглядаючи багато речень, такі слова, як dog, puppy і hound, мають спільні корисні шаблони контексту. Модель передбачення може повторно використовувати параметри в цих шаблонах. Спільний контекст може вказувати на подібність, а також на тематичну асоціацію чи граматичну сумісність.

Питання полягає в тому, як це ефективно обчислити. Класичною відповіддю, що повертається до початку 1990-х років, були дуже великі розріджені матриці спільного входження — латентний семантичний аналіз і його родичі. Для речення “кіт сидів на килимку” з віконцем із 2 слів (перегляньте до 2 лексем ліворуч і праворуч від кожного центрального слова) матриця виглядає так:

                the     cat     sat     on     mat
the         [    0,      1,      2,      1,     1 ]
cat         [    1,      0,      1,      1,     0 ]
sat         [    2,      1,      0,      1,     0 ]
on          [    1,      1,      1,      0,     1 ]
mat         [    1,      0,      0,      1,     0 ]

Кожен M[i,j] підраховує випадки появи слова j у вікні слова i. Ми виключаємо центральну позицію, а не кожне повторення того самого слова. У цьому прикладі діагональ дорівнює нулю; повторювані слова у вікні можуть створювати ненульові діагональні записи.

Матриця створюється шляхом переміщення вікна по корпусу та підрахунку.

window half-width:(5 words wide)
accumulated matrix · 2 of 18 co-occurrences tallied
the
cat
sat
on
mat
the
0
1
1
0
0
cat
0
0
0
0
0
sat
0
0
0
0
0
on
0
0
0
0
0
mat
0
0
0
0
0

У кожній позиції з центральним словом i подивіться на слова у вікні ±W і для кожного сусіда j у цьому вікні збільште M[i][j] на одиницю. Прокрутіть віджет від початку до кінця, і матриця заповнить комірку за коміркою — один прохід крізь корпус, і кожне підрахунок одночасних випадків буде записано.

Це перший крок конвеєра підрахунку, а потім розкладання на множники, а на його вершині є ще два. Форма вектора слова суттєво змінюється на кожному етапі:

Етап 1 — необроблені підрахунки. Матриця вище, як вона є. Кожен рядок складається з V чисел (скорочено «V-довгий»), одне місце для кожного слова в словнику — це вже вектор слова, просто надзвичайно великий. Кожен запис буквально означає «скільки разів це конкретне словникове слово з’являлося у вікні ±2 цього слова по всьому корпусу». Рядок для cat — [1, 0, 1, 1, 0]. Корпус іграшок містить лише шість жетонів, тому їх кількість невелика, але структура має значення в масштабі.

Стадія 2 — коригування частоти. PMI порівнює спостережувану ймовірність спільного виникнення з добутком її граничних значень. Неспостережувані пари мають невизначений або негативно-нескінченний вихідний PMI, тому практична матриця часто використовує позитивний PMI, PPMI = max(PMI, 0), призначаючи нуль неспостережуваним записам. Вибір згладжування та зважування впливає на результат.

Етап 3 — стиснення. Усічений SVD апроксимує зважену матрицю за допомогою її найбільших сингулярних значень і відповідних сингулярних векторів. Загальною таблицею векторів слів є U_d Σ_d. Це наближення низького рангу, а не автоматичний PCA: PCA спочатку центрує дані, тоді як ця матрична факторізація не потрібна.

raw cat counts:     [1, 0, 1, 1, 0]
PPMI (rounded):     [0.182, 0, 0.405, 0.405, 0]
SVD representation: a row of U_d @ Sigma_d

Отримані координати d поєднують інформацію з багатьох контекстних слів. Індивідуальні координати не обов’язково мають такі назви, як «роялті» або «стать».

Факторизація на основі кількості та word2vec створюють щільні таблиці, але оптимізують різні цілі. Зміщений PMI-зв’язок застосовується саме до скіп-граму з негативною вибіркою за заявлених припущень; це не робить кожен варіант word2vec еквівалентним SVD. Ми знаходимо цей зв’язок у статті про негативну вибірку.

Матриця слово-контекст має V×V можливих записів, але розріджене зберігання дозволяє уникнути розподілу кожного нуля. Розріджений підрахунок і факторизація все ще можуть бути дорогими для великих корпусів. Word2vec пропонує інший шлях: оновлювати вектори безпосередньо з навчальних прикладів без побудови цієї матриці.

word2vec

Word2vec вивчає свою таблицю вбудовування, передбачаючи слова в локальних вікнах. Ми почнемо з повної моделі softmax, щоб зробити прямий прохід і градієнти явними, а потім виділимо більш дешеві цілі, які використовуються в практичних реалізаціях.

Сам пошук дає слову один вектор незалежно від його пропозиції. Модель послідовності нижче за течією може поєднувати ці вектори з порядком слів і контекстом. Таким чином, статичні вкладення є початковим представленням, а не повною моделлю речення.

Word2vec пропонує дві альтернативні архітектури навчання:

  • Skip-gram. Маючи центральне слово, передбачте слова у маленькому віконці навколо нього. Ми подаємо центральне слово як вхідні дані та по черзі прогнозуємо кожне з навколишніх слів контекстного вікна — кожна пара (center, context) є окремим навчальним прикладом, тому те саме центральне слово повторно використовується один раз для кожного сусіда.
  • CBOW (безперервний мішок слів). Зворотне: задані слова у віконці передбачте центральне слово. Ми вводимо набір контекстних слів і прогнозуємо одне слово в середині. Цю форму заповнення — відновити відсутнє слово з його оточення — це те, що моделювання маскованої мови BERT пізніше успадковує та масштабує.

Обидва роблять те саме, просто виконують прогноз у протилежних напрямках. Ми пропрацюємо skip-gram від кінця до кінця, а потім повернемося до CBOW.

Навчальні дані та одноразові входи

Увесь конвеєр пропуску грамів чітко розділяється на два етапи з чіткою межею: попередня обробка перетворює необроблений текст на список навчальних прикладів, а потім навчання запускає ці приклади через нейронну мережу.

Давайте спочатку розглянемо попередню обробку — перетворення необробленого тексту на довгий список пар цілих чисел. Нейронна мережа ще не задіяна:

  1. Tokenize the corpus — розділіть текст на список токенів слів (відокремлених пробілами, зазвичай у нижньому регістрі; word2vec використовує токени на рівні слів, а не частини підслів).
  2. Створіть словниковий запас — призначте кожному окремому токену цілий ідентифікатор; цей ідентифікатор згодом слугуватиме індексом маркера в векторах з одним функціоналом і як номер рядка в матриці вбудовування. Загальна кількість V (розмір словникового запасу).
  3. Витягти (center, context) пар — перемістіть вікно над потоком ідентифікаторів і випустіть один навчальний приклад для кожного сусіда.

Для цього прикладу ми вибираємо вікно з двох жетонів з обох боків. Розмір вікна є гіперпараметром; практичні реалізації можуть також відбирати менший ефективний радіус і робити підвибірку частих слів.

Використовуючи речення “кіт сидів на килимку” як приклад, ми встановлюємо вікно на ±2 і пересуваємо його слово за словом. З вікном, центрованим на sat, сусідами є the, cat, on, the — ця єдина позиція випромінює чотири пари: (sat, the), (sat, cat), (sat, on), (sat, the). Зробіть крок вперед до on, і вікно знайде cat, sat, the, mat — ще чотири пари. Знову крок, ще чотири і так далі, доки цілий корпус не згорнеться в довгий список пар (center, context), створених повністю з самого тексту, без жодної людини, яка ніколи щось маркує.

skip-gram windowing · slide the window over a passage, collect the training examples
window half-width:(5 words wide)
The green word is the center. The blue words are its neighbours inside the window. Every center → neighbour pairing is one training example. Step the window across the text (the controls below, or click any word) and they pile up into the training set.
pairs from this center
(the → cat)(the → sat)
training set so far — 2 of 18 pairs
the→catthe→sat

Отримані пари цілих чисел вписуються в короткий цикл Python:

# Step 1: tokenize.
corpus = "the cat sat on the mat"
tokens = corpus.split()
# ['the', 'cat', 'sat', 'on', 'the', 'mat']

# Step 2: build vocabulary and convert tokens to integer IDs.
vocab = sorted(set(tokens))                       # ['cat', 'mat', 'on', 'sat', 'the']
word2id = {w: i for i, w in enumerate(vocab)}     # {'cat': 0, 'mat': 1, 'on': 2, 'sat': 3, 'the': 4}
V = len(vocab)                                    # 5
ids = [word2id[w] for w in tokens]                # [4, 0, 3, 2, 4, 1]

# Step 3: slide a ±2 window over the ID stream, emit (center, context) pairs.
window = 2
pairs = []
for i, c in enumerate(ids):
    for j in range(max(0, i - window), min(len(ids), i + window + 1)):
        if i != j:
            pairs.append((c, ids[j]))

len(pairs)  # 18 — exactly the (center_id, context_id) pairs the widget above emits.

Попередня обробка, по суті, однакова, незалежно від того, який варіант word2vec ви тренуєте далі — токенізація, словниковий запас і вікна ідентичні. Відрізняється лише формат випущених прикладів: skip-gram пакує їх як пари, тоді як CBOW видає один контекстний пакет плюс його центр на вікно. Навчання – це місце, де фактично живе алгоритм, і в решті цього розділу він детально розглядається.

Кожна пара є навчальним прикладом

Виконавши попередню обробку, давайте подивимося, як алгоритм навчання використовує ці пари.

Кожна пара містить вхідне слово та цільову мітку. Вікно навколо sat видає чотири приклади з цілями the, cat, on, the — три різні слова, де the зустрічається двічі. У моделі повного softmax ціль використовується для втрати, а не для кроку вперед.

Це схоже на MNIST: кожен приклад MNIST поєднує зображення з його цифровою міткою, а тут кожна пара пропуску грамів (c, t) поєднує центральне слово c (вхід) з одним контекстним словом t (ціль). Для (sat, cat): подати sat, отримати передбачуваний розподіл за словником, порівняти його з cat, зробити крок SGD. Потім наступна пара.

Перш ніж ми зможемо щось передати в мережу, нам потрібно представити слово як вектор чисел. У MNIST цей крок здебільшого безкоштовний — зображення вже є сіткою інтенсивності пікселів, тому ми просто зводимо його у вектор із 784 чисел. Слово не має внутрішнього числового вмісту, тому ми винайшли його ще на кроці створення словника вище — кожне слово вже має цілий індекс від 0 до V−1. Щоб передати його в мережу, ми розширюємо цей індекс до одногарячого V-вектора — вектор V чисел, усі нулі, крім одного 1 в індексі слова.

Для нашого поточного словника з 5 слів (V=5) кодування виглядає так:

"cat" → [1, 0, 0, 0, 0]
"mat" → [0, 1, 0, 0, 0]
"on"  → [0, 0, 1, 0, 0]
"sat" → [0, 0, 0, 1, 0]
"the" → [0, 0, 0, 0, 1]

На щастя, є розумна частина лінійної алгебри, яка дозволяє нам взагалі пропустити побудову одноразового вектора — множення одноразового вектора на матрицю є таким самим, як вибір одного рядка цієї матриці (пошук за цілим ідентифікатором слова). Для нашого словника з 5 слів, з sat в індексі 3 і деякою матрицею M форми (5, d):

       one-hot for "sat"            M  (5 rows × 3 cols)            result
[ 0  0  0  1  0 ]    ·    [ row 0:  0.21  -0.43   0.15 ]    =    [ 0.33  -0.27   0.84 ]
                          [ row 1:  0.07   0.62  -0.31 ]              (just row 3)
                          [ row 2: -0.55   0.18   0.40 ]
                          [ row 3:  0.33  -0.27   0.84 ]
                          [ row 4: -0.12   0.49  -0.06 ]

Кожен член, який торкається 0 з одного гарячого, зникає, залишаючи лише внесок рядка 3 — отже, відповіддю буде просто рядок 3 M.

# dot product, column by column:
  col 0:  0·0.21  + 0·0.07  + 0·(-0.55) + 1·0.33  + 0·(-0.12)  =  0.33
  col 1:  0·(-0.43) + 0·0.62 + 0·0.18   + 1·(-0.27) + 0·0.49   = -0.27
  col 2:  0·0.15  + 0·(-0.31) + 0·0.40  + 1·0.84  + 0·(-0.06)  =  0.84

Ментальна модель — те, що ми намагаємося зробити

Для кожної пари (center, target) оцініть ціль проти конкуруючих словникових слів і налаштуйте дві таблиці вбудовування, щоб зменшити втрати передбачення.

Навчання використовує ненормалізований скалярний добуток, а не косинусну подібність. Косинус ділить цей скалярний добуток на обидві векторні норми і зазвичай використовується для порівняння вкладень після навчання. Softmax перетворює оцінки навчання на розподіл, зберігаючи їх порядок.

У нас буде дві вагові матриці, які можна навчити — кожна між парою шарів у мережі — разом надаючи кожному слову словника два вектора d-dim (одне вбудовування на роль):

  • E форми (V, d) — кожен ряд є введенням одного слова, яке використовується, коли слово з’являється як центр навчальної пари (слово, яке обумовлюється).
  • E' форми (d, V) — кожен стовпець є вихідним вбудовуванням одного слова, яке використовується, коли слово з’являється як цільовий/контекст, що прогнозується (слово оцінюється як кандидат).

E і E' є незалежними матрицями параметрів, які не транспонують одна одну. Різні макети просто поміщають вхідні вектори в рядки, а вихідні — у стовпці. Слово має один тренувальний вектор для кожної ролі.

Окремі таблиці дозволяють слову виконувати різні вхідні та вихідні ролі. Після навчання програми зазвичай використовують E; використання вихідних векторів або об’єднання двох таблиць також можливо. Нижче завдання визначає, яке подання є корисним.

Віджет нижче робить цей крок конкретним — обчислення подібності між вимірами між центральним словом і кожним словом у словнику, взаємодія між E і E'. Ми зосереджуємось на sat як у центрі (виділено зеленим у E). Перейдіть, щоб спостерігати, як matmul оцінює його за кожним словом у словнику — по одному скалярному добутку на слово, заповнюючи векторний запис оцінки за записом. (Лише етапи 1 і 2 — softmax і втрата з’являться кількома розділами пізніше.)

1 of 5 scores computed
E (V=5 × d=3)
row sat = v_c
cat
0.21
-0.43
0.15
mat
0.07
0.62
-0.31
on
-0.55
0.18
0.40
sat
0.33
-0.27
0.84
the
-0.12
0.49
-0.06
E' (d=3 × V=5)
one column per word — v_c is dotted with each in turn
cat
mat
on
sat
the
0.45
-0.31
0.18
-0.22
0.07
0.62
0.15
-0.40
0.33
0.55
-0.20
0.48
0.27
0.11
-0.39
scores (V=5)
cat
mat
on
sat
the
-0.19
·
·
·
·
dot product for cat
score[cat] = E[sat, :] · E'[:, cat]
= 0.33 · 0.45 + (-0.27) · 0.62 + 0.84 · (-0.20)
= 0.149 + (-0.167) + (-0.168)
= -0.187

Оцінка v_c · v'_t. Із вихідними векторами, що зберігаються як стовпці, scores = v_c @ E' обчислює всі оцінки словника одночасно. Якщо вони зберігаються як рядки в Ep, еквівалентним виразом є scores = Ep @ v_c; цикл Python не потрібен ні в одному макеті.

Подібно до вихідного рівня класифікатора, кожен кандидат має вектор ваги, який оцінює приховане представлення. Розміри вбудовування вивчаються спільно і, як правило, не відповідають індивідуально названим семантичним ознакам.

Архітектура

Після того, як ми зібрали пари, ми, по суті, запускаємо контрольоване завдання передбачення з мітками — якщо центральне слово передбачає, яке слово зі словника буде поруч — тому налаштування подібне до MNIST у ключових аспектах: один прихований шар, softmax над класами виводу, перехресна ентропія щодо мітки. Що відрізняється, так це масштаб (розмір словника V тут проти 10-значних класів для MNIST), формат введення (один гарячий проти щільних дійсних пікселів) і ціль (нам потрібні навчені вбудовування, а не сам прогноз).

Архітектурно word2vec (2013) є двошаровою прямою нейронною мережею — прямою мережею двох повністю зв’язаних рівнів (дві вагові матриці E і E'), з потоком даних вхід → прихований → вихід і без петель. Це те саме сімейство, що й класифікатор MNIST, з однією особливістю: прихований шар є суто лінійним, тому він вивчає кодування слів, а не діє як нелінійний екстрактор ознак.

Конкретно, вхід є V-dim one-hot для центрального слова; прихований шар має d нейронів (наприклад, 300) і є чисто лінійним (без зміщення, без нелінійності); вихід має V нейронів із softmax по всіх V, що створює P(w | c) — ймовірність кожного словникового слова в центрі. Дві матриці E і E', які ми представили в попередньому розділі, живуть між цими шарами: E — вхідна → прихована вагова матриця (форма (V, d)), E' — прихована → вихідна матриця (форма (d, V)). Це єдині вивчені параметри мережі; обидва починаються випадково, і після навчання лише E постачається як остання таблиця вбудовування слів — E' скидається.

Ось повна архітектура softmax у Keras, використовуючи наведений вище словник іграшок:

from tensorflow import keras
from tensorflow.keras import layers

V = len(vocab)  # five words from the example above
d = 3

model = keras.Sequential([
    keras.Input(shape=(1,), dtype='int32'),         # integer ID of the center word
    layers.Embedding(input_dim=V, output_dim=d),    # E:  shape (V, d), the lookup
    layers.Reshape((d,)),                           # (1, d) → (d,)
    layers.Dense(V, use_bias=False),                # E': shape (d, V), the linear layer
    layers.Softmax(),                               # softmax over V vocab scores
])

model.compile(optimizer='sgd', loss='sparse_categorical_crossentropy')

Embedding(V, d) шукає вхідні вектори; Dense(V, use_bias=False) зберігає вихідні ваги. Вхідні дані мають форму (batch, 1), тому вихідні дані вбудовування є (batch, 1, d). Reshape видаляє цю вісь послідовності з одним маркером перед підрахуванням балів. Між двома тренуваними шарами немає нелінійної активації.

Softmax перетворює вихідні оцінки V на розподіл ймовірностей у словниковому запасі, а sparse_categorical_crossentropy — це стандартна втрата класифікації на додачу до цього — та сама softmax-plus-cross-entropy, яку використовує MNIST, тільки з V класами словника замість 10 цифр. Ми детально розглянемо втрату та її градієнт у наступних розділах.

Розмір вбудовування d — це гіперпараметр, який контролює ємність, пам’ять і обчислення. Кілька сотень розмірів є звичайним, але відповідний розмір залежить від корпусу та завдання; це не фіксується лише розміром словникового запасу.

Те, що ми щойно описали, це передача в базовому регістрі — softmax для всього словника на стороні виведення.

Передача вперед — пошук, оцінка, softmax

Тепер, коли ми розглянули високорівневу архітектуру — одноразовий вхід, прихований пошук, вихідні оцінки — давайте збільшимо масштаб і розглянемо, що саме відбувається, коли один навчальний приклад проходить через мережу. Для центрального слова c передача вперед рухається зліва направо в три етапи: пошук, підрахунок і softmax.

Віджет у розділі ментальної моделі уже показував перші два етапи ізольовано — одноразова матриця разів згортається до зчитування рядків, а потім матриця рядків і разів створює V балів. Тут ми називаємо їх, додаємо softmax зверху та слідуємо цифрам від кінця до кінця.

Етап 1: введення × E → прихований (пошук). Математично це множення матриці one_hot(c) @ E, що створює прихований вектор d-dim. Вхідні дані є розрідженими — записи V−1 дорівнюють нулю — тому майже кожне множення обчислюється як нуль, а весь матмул (V, d) згортається до одного рядка: v_c = E[c].

Етап 2: прихований × Е’ → бали словникового запасу. scores = v_c @ E' обчислює один скалярний добуток на вихідне слово, що коштує O(Vd). Прохід назад може надати градієнти кожному вихідному стовпцю та вибраному вхідному рядку.

Матмул ніколи не дивиться на цільове слово. Він використовує лише v_c = E[sat] і оцінює sat за всім словником, створюючи всі бали V одночасно. Таким чином, ці п’ять балів — cat -0.19, mat 0.26, on 0.39, sat -0.07, the -0.45 — ідентичні для кожної тренувальної пари, яка поділяє центр sat: (sat, cat), (sat, on) і (sat, the) усі виконують той самий матмул і приземляються на ті самі п’ять чисел. Цільове слово входить лише пізніше, при втраті; передній пас ніколи не бачить його.

Етап 3: softmax → ймовірності. Оцінки V, які називаються логітами, є довільними дійсними числами: вони можуть бути від’ємними, необмеженими, не підсумовуватись ні до чого конкретного. Softmax перетворює їх на P(w | c) — V невід’ємних чисел, сума яких дорівнює 1, передбачувана моделлю ймовірність того, що слово w знаходиться в контексті c.

Використовуючи той самий словник із 5 слів, віджет нижче проходить усі три етапи. Виберіть тренувальну пару (center, context), а потім натисніть крок, щоб заповнити вектор балів по одному скалярному добутку; як тільки всі оцінки V будуть введені, softmax перетворює їх на ймовірності.

training pair
E (V=5 × d=3)stage 1 · lookup
row sat = v_c
cat
0.21
-0.43
0.15
mat
0.07
0.62
-0.31
on
-0.55
0.18
0.40
sat
0.33
-0.27
0.84
the
-0.12
0.49
-0.06
E' (d=3 × V=5)stage 2 · score
column cat = v'_w
cat
mat
on
sat
the
0.45
-0.31
0.18
-0.22
0.07
0.62
0.15
-0.40
0.33
0.55
-0.20
0.48
0.27
0.11
-0.39
scores (V=5)
cat
mat
on
sat
the
-0.19
0.26
0.39
-0.07
-0.45
P (V=5)stage 3 · softmax
cat
mat
on
sat
the
0.16
0.25
0.29
0.18
0.12
sum = 1.00

У цьому числовому прикладі, обраному вручну, on має найвищий бал, приблизно 0.39. Цифри ілюструють розрахунок; вони не є доказом того, що ця крихітна модель засвоїла речення.

У скіп-грамі ці показники є скалярними добутками v_c · E'[:, w] для кожного словникового слова w, а результуюча ймовірність P(w | c). Витягнувши лише крок softmax із наведеного вище віджета, п’ять оцінок [-0.19, 0.26, 0.39, -0.07, -0.45] перетворюються на правильний розподіл ймовірностей, сума якого дорівнює 1:

softmax · the operation that turns scores into a probability distribution
1. raw scores sw (any real number — these are the V scores from the matmul above)
cat
-0.19
mat
0.26
on
0.39
sat
-0.07
the
-0.45
2. apply exp: esw (all positive; large scores blow up, negative scores shrink toward 0)
cat
0.83
mat
1.30
on
1.48
sat
0.93
the
0.64
3. divide by the sum: esw / Σ es (probabilities — non-negative, sum to 1)
cat
0.160
mat
0.251
on
0.286
sat
0.180
the
0.123

Softmax підносить бали до степеня та ділить на їх суму. Реалізації спочатку віднімають найбільший бал для чисельної стабільності; це залишає ймовірності незмінними. Сума результату дорівнює одиниці, але нормалізація сама по собі не гарантує добре відкаліброваних прогнозів.

Функція втрат

Втрата для одного навчального прикладу з цільовим словом t є негативною логарифмічною ймовірністю, яку модель присвоїла цій меті:

loss = −log P(t | c)

Це дає нам одне число на навчальну пару (c, t) — маленьке, коли softmax накопичує ймовірність на справжню ціль, велике, коли цього не відбувається. Це крос-ентропія з одноразовою міткою — та сама втрата, яку використовує MNIST, і градієнт через softmax виводиться там.

Конкретно, для пари (sat, cat), використовуючи бали та ймовірності, наведені вище:

                  cat    mat    on    sat    the
scores        = [-0.19,  0.26,  0.39, -0.07, -0.45]
probabilities = [ 0.16,  0.25,  0.29,  0.18,  0.12]
target        = cat
P(cat | sat)  = 0.16
loss          = −log(0.16) ≈ 1.83

# what-ifs — how the loss responds to different P(cat):
P(cat) = 0.90  →  loss = −log(0.90) ≈ 0.11   (good prediction)
P(cat) = 0.01  →  loss = −log(0.01) ≈ 4.6    (bad prediction)

Наведений нижче віджет передає п’ять імовірностей softmax зверху та відображає їх на кривій −log. Клацніть інше слово, щоб позначити його як ціль — маркер ковзає вздовж кривої, і ви можете безпосередньо побачити, як ціль, яку вже віддає перевагу модель, майже нічого не коштує, тоді як ціль, яку вона має недостатньою вагою, платить високу ціну.

loss · how a single probability becomes a single number
plug P(cat) into −log: large probability → tiny loss, tiny probability → huge loss
01234500.250.50.751P(target)loss = −log Puniform · (1/V, log V)(0.16, 1.83)
loss = −log P(cat) = −log(0.16) = 1.83

Збиток зростає, коли цільова ймовірність наближається до нуля. Проте після диференціювання через softmax градієнт відносно логіту P(w) − 1[w=t], обмежений між −1 і 1. Градієнти параметрів також залежать від залучених векторів.

Рівномірний розподіл за п’ятьма словами призначає ймовірність 0.20 і втрату log(5) ≈ 1.61 будь-якій цілі. Наш приклад дає cat близько 0.16, тому його втрати вищі. Рівномірний вихід є корисним посиланням, а не властивістю кожної випадкової ініціалізації.

Градієнт

Градієнт подібний до MNIST: градієнт втрат щодо кожного логіта дорівнює P(w) − 𝟙[w == t] — прогнозована ймовірність мінус одна гаряча ціль. Градієнт цільового слова P(t) − 1 (негативний — підвищити оцінку); кожне інше слово є P(w) (позитивне — зменште його оцінку, пропорційно до того, яка його ймовірність на даний момент). Слова, які модель вже правильно думає, навряд чи ледве рухаються; слова, про які це неправильно, отримують найбільший сигнал.

Наведений нижче віджет робить це віднімання конкретним на тих самих п’яти ймовірностях згори. Клацніть іншу ціль, щоб побачити перемальовування рядка градієнта — одна висока синя смуга тягне оцінку цілі вгору, чотири короткі червоні смуги штовхають інші вниз.

gradient · subtracting the one-hot target from the softmax distribution
pick a target word — the model is trying to push P(cat) toward 1
∂loss / ∂score[w] = P(w) − 𝟙[w == t]
P(w)
probability the softmax assigned
cat
0.16
mat
0.25
on
0.29
sat
0.18
the
0.12
−
𝟙[w == t]
one-hot for the true target word
cat
1.00
mat
0.00
on
0.00
sat
0.00
the
0.00
=
gradient
positive → SGD pushes score down · negative → SGD pulls score up
cat
-0.84
mat
0.25
on
0.29
sat
0.18
the
0.12

Backprop переносить ці градієнти балів у E' і E за правилом ланцюга:

scores = v_c @ E'                       (the forward step we're differentiating)

∂loss / ∂E'[:, w]  =  ( P(w) − 𝟙[w == t] ) · v_c       ← gradient on column w of E'
∂loss / ∂v_c       =  E' @ ( P − one_hot_t )           ← gradient into the hidden vector
∂loss / ∂E[c]      =  ∂loss / ∂v_c                     ← because v_c = E[c]

Варто пам’ятати про два наслідки, обидва з одного гарячого входу:

Пошук дає градієнт лише для рядка E[c]; інші вхідні рядки мають нульовий градієнт для цього прикладу. Повний softmax включає кожен стовпець E'. Ці градієнти описують скалярний добуток; вони не гарантують, що кожна попарна відстань змінюється в напрямку, запропонованому метафорою «тягнути/штовхати».

Потім ці градієнти використовуються для виконання кроку градієнтного спуску: застосовуйте їх до ваг як E -= lr × ∂L/∂E і E' -= lr × ∂L/∂E'. Це робота оптимізатора, і вибір оптимізатора (vanilla SGD, momentum, Adam, RMSprop, AdaGrad…) має значення лише на цьому кроці — усі вони споживають однакові градієнти, але використовують їх по-різному.

Міні-партії та епохи

Покрокове керівництво вище обробляло пари по одній. Більшість навчання NN узагальнює це в міні-пакет SGD — групує приклади в пакети B і обробляє весь пакет за один прохід вперед + назад, точно такий самий міні-пакет SGD, як і MNIST, лише усереднене для B прикладів на крок. Це рецепт, який використовують BERT, GPT і майже всі сучасні моделі, і це те, що описано нижче.

Оригінальна реалізація word2vec і Gensim використовують оптимізоване навчання процесора з асинхронними оновленнями робітників. Наведений нижче повний опис міні-пакета softmax є загальною довідковою реалізацією, а не описом внутрішнього циклу Gensim.

Епоха – це проходження навчального корпусу або підготовлених прикладів. У реалізаціях, які змінюють вибірку контекстних вікон або відкидають часті лексеми, послідовні епохи не повинні містити абсолютно однакові пари слів.

Per-batch, the loop looks like this:

  1. Передача пакету прикладів B — кожна пара (c, t) проходить через мережу. У векторизованому вигляді пошук поєднує B рядків із E у матрицю (B, d), крок оцінки стає одним матулем (B, d) @ (d, V) → (B, V), а softmax виконується по рядках.
  2. Втрата — обчисліть втрату перехресної ентропії для кожного прикладу (така ж формула, що й B=1), а потім усередніть для партії в один скаляр.
  3. Зворотній перехід (backprop). Обчислюйте градієнти за допомогою правила ланцюга — чисте обчислення від втрати до кожної ваги, поки не оновлюється вага. Вихід: градієнтні тензори ∂L/∂E і ∂L/∂E', такої самої форми, як і вагові тензори.
  4. Крок градієнтного спуску — застосуйте градієнти до ваг за допомогою оптимізатора, точно так само, як у випадку однієї пари вище. Repeat for the next batch.

Запустіть цей цикл через багато пакетів на епоху та кілька епох у корпусі, і рядки E і E' розмістяться в геометрії, якій надає перевагу втрата.

CBOW — and the bridge to BERT

CBOW (continuous bag of words) — це другий алгоритм word2vec, який працює як дзеркальне відображення skip-gram. Якщо skip-gram бере центральне слово та передбачає цільове слово як його контекст, CBOW приймає кілька слів як контекст і передбачає центр — відсутнє слово.

Моделювання маскованої мови CBOW і BERT передбачають слово чи лексему з навколишнього контексту. Їх представлення відрізняються: CBOW усереднює невеликий невпорядкований пакет контексту, тоді як BERT будує чутливі до позиції контекстні стани за допомогою трансформатора. BERT is not simply CBOW with a larger window.

Механічно CBOW відрізняється від skip-gram точно одним кроком усереднення на вході; the rest of the training loop is identical. Таким чином, більшість розділів пропуску грамів, наведених вище, залишаються незмінними — тут ми зосереджуємось лише на відмінностях CBOW.

Попередня обробка — контекстні пакети замість пар

Конвеєр попередньої обробки такий самий, як і skip-gram: токенізація, створення vocab, переміщення вікна над потоком маркерів. Змінюється форма того, що випромінюється на кожну позицію вікна:

Position centered on:    Skip-gram emits (per position):       CBOW emits (per position):
─────────────────────    ──────────────────────────────       ──────────────────────────
the      (pos 0)         (the, cat), (the, sat)                ({cat, sat}, the)
cat      (pos 1)         (cat, the), (cat, sat), (cat, on)     ({the, sat, on}, cat)
sat      (pos 2)         (sat, the), (sat, cat),               ({the, cat, on, the}, sat)
                         (sat, on), (sat, the)
on       (pos 3)         (on, cat), (on, sat),                 ({cat, sat, the, mat}, on)
                         (on, the), (on, mat)
the      (pos 4)         (the, sat), (the, on), (the, mat)     ({sat, on, mat}, the)
mat      (pos 5)         (mat, on), (mat, the)                 ({on, the}, mat)
                         ─────────────────────                  ────────────────
                         total: 18 pairs                         total: 6 examples

Той самий корпус, те саме вікно — skip-gram створює 18 окремих навчальних пар (center, neighbour), CBOW створює 6 прикладів (context_bag, center). Step through it in the widget:

CBOW windowing · slide the window over a passage, collect the training examples
window half-width:(5 words wide)
The blue words are the context; their vectors get averaged into one input vector. The green word is the center — the prediction target. Each window position emits one example: context → center. Step the window across the text (the controls below, or click any word) and they pile up into the training set.
training example at this position
(cat, sat) → the
training set so far — 1 of 6 examples
(cat sat)→the

Архітектура

Архітектура CBOW — це skip-gram з перевернутими кінцями — вхідні дані є контекстними словами, вихідні — центральним словом. Ті самі дві вагові матриці E і E', однакова розмірність d, той самий softmax над V. Єдина архітектурна відмінність на стороні введення: skip-gram шукає один рядок E (центр), CBOW шукає C рядки (по одному на контекстне слово) і усереднює їх у єдиний d-dim hidden vector h:

Skip-gram:  h = E[c]                                  (one row read)
CBOW:       h = mean(E[c_1], E[c_2], ..., E[c_C])     (C rows read, then averaged)

Візуалізовано нижче — одноразові входи C, відповідні пошуки рядків C у E і крок усереднення, який створює h:

CBOW · dim = d, vocab = V
input
the
1
0
0
…
cat
0
1
0
…
on
0
0
1
…
the
1
0
0
…
the C context words, one-hot each
shape: C × V
lookup C rows
input embedding E
learned table
shape: V × d
the C rows
average them → h
mean ( + )
↓
0.05
-0.11
0.22
0.31
-0.04
0.18
-0.09
0.12
h = mean(vthe, vcat, …) ∈ ℝd
shape: d

Усереднення — це те, що дає назву «мішку слів» — порядок слів у вікні відкидається, контекст стає мультимножиною. На кожному кроці навчання виконується пошук поточних значень E — рядки, які ви читаєте під час прямого проходу, є тими самими рядками, які ви оновлюєте під час зворотного проходу, як і будь-яке навчання SGD нейронної мережі. Після обчислення h усе, що йде внизу — scores = h @ E', softmax, втрата перехресної ентропії, зворотна пропозиція, SGD на E і E', а також ярлик негативної вибірки — ідентично пропусканню грама.

Тренування — три невеликі відмінності від скіп-грама

Прямий проход, втрата, градієнт, міні-дозування та скорочення негативної вибірки переходять із skip-gram без змін. Three differences worth keeping in mind:

CBOW робить одне передбачення на вікно, тоді як skip-gram робить одне передбачення на входження контексту, тому CBOW часто вимагає менше роботи з вихідним рівнем. З вектором середнього контексту кожен входження отримує grad_h / C; Ідентифікатори повторюваних слів повинні накопичувати свої внески. Повний softmax все ще оновлює всі вихідні стовпці, тоді як негативна вибірка оновлює вибрані вихідні вектори. Відносна швидкість і якість вбудовування залежать від даних і налаштувань.

Як word2vec насправді навчається на практиці — Gensim

Word2Vec від Gensim обробляє побудову словника, контекстну вибірку, негативну вибірку та векторний пошук. Його вхідні дані sentences — це ітерація токенізованих речень. This tiny example checks the API; він занадто малий, щоб вивчати корисні семантичні аналогії.

from gensim.models import Word2Vec

sentences = [
    "the king sat in the palace".split(),
    "the queen sat in the palace".split(),
    "the king and the queen ruled".split(),
]
model = Word2Vec(sentences, vector_size=50, window=2, min_count=1,
                 sg=1, negative=5, hs=0, workers=1, seed=42, epochs=20)
v_king = model.wv["king"]

CPU training and memory access

При негативній вибірці одна пара торкається лише кількох векторів вбудовування. Many implementations store both tables by row; з макетом стовпців, який використовувався раніше, вихідний доступ є E'[:, w]. The work per pair is approximately:

1. Read the input vector:       E[center]
2. Read output vectors:        target + k sampled negatives
3. Compute scores:             k+1 dot products, each length d
4. Accumulate gradients:       one input row and sampled output vectors
5. Apply the SGD update:       repeated indices receive summed contributions

Дві таблиці вбудовування float32 вимагають 2 × V × d × 4 байт: близько 2,4 ГБ для одного мільйона слів і 300 розмірів без урахування словника та накладних витрат оптимізатора. Невеликі нерегулярні оновлення можуть підходити для реалізацій ЦП, але продуктивність графічного процесора залежить від пакетування, розташування пам’яті та реалізації. Не існує незалежного від апаратного забезпечення правила, згідно з яким word2vec має бути швидшим на ЦП.

Alternatives to full softmax

Пояснювальна мережа вище оцінює кожне словникове слово, вартістю O(Vd) за приклад. У прикладі Gensim замість цього використовується негативна вибірка. Два встановлені способи уникнути повного підрахунку словникового запасу:

Негативна вибірка замінює класифікацію словника вибірковими двійковими мітками, вартістю O((k+1)d) для одного позитивного та k негативного. It learns a different objective; це не неупереджений ярлик до повного градієнта softmax. У наступній статті описується втрата та надається робоче оновлення.

Ієрархічний softmax надає словам листя бінарного дерева та призначає вектор кожному внутрішньому вузлу. Імовірність слова множить відповідні ймовірності лівої/правої гілки на його шляху, використовуючи один скалярний добуток на вузол. A balanced tree gives paths of order log₂V; Дерево Гаффмана word2vec дає короткі шляхи для частих слів. Листкові ймовірності утворюють нормалізований розподіл.

Ієрархічний softmax залишається доступним у Gensim, наприклад, з hs=1, negative=0. Вибір залежить від вартості навчання та представлень, яких потребує завдання; це не застарілий алгоритм, який повсюдно програв негативній вибірці.

Де порушуються статичні вбудовування

word2vec створює статичні вбудовування: один фіксований вектор на слово, незалежно від контексту. Це саме правильна форма для гіпотези розподілу, як було сказано спочатку, але вона має три режими невдач, які ставали все більш помітними, коли НЛП переходило до складніших завдань.

Багатозначність

Розгляньте ці два речення:

  • Я вніс чек у банк.
  • Влаштували пікнік на берегу річки.

Статичне вбудовування дає bank один вектор для обох речень. Він змішує докази з різних вживань, не визначаючи, який сенс мається на увазі в цьому випадку. Ця суміш не обов’язково є середнім арифметичним окремих векторів відчуттів.

cosine similarity of bank with two sense clusters
"bank" as financial institution
money
0.436
loan
0.418
account
0.403
deposit
0.451
interest
0.409
"bank" as river bank
river
0.273
shore
0.284
water
0.159
creek
0.145
flood
0.199
the financial sense dominates — that's the corpus skew, not a deep fact about the word. but both senses pull above zero from the same vector, because there's only one vector to give. a static embedding can't tell the model which sense is meant in any specific sentence; that's left to whatever sits on top.
cosine similarities computed on glove-wiki-gigaword-300

Віджет порівнює bank зі словами, пов’язаними з фінансами та річкою, в одній попередньо навченій моделі. Ці подібності відображають цю модель і корпус. Нижня модель, яка також бачить навколишні слова, все ще може усунути неоднозначність речення; сам по собі статичний вектор не забезпечує такої різниці.

Немає чутливості до синтаксису

Представлення сумки векторів відкидає порядок слів. Речення:

  • Собака кусає людину.
  • Людина кусає собаку.

мати той самий пакет слів, але різні порядки лексем. Усереднення їхніх статичних векторів дає те саме представлення. Послідовність векторів зберігає порядок, який може використовувати RNN, згортка або трансформатор.

Охоплення та адаптація словникового запасу

«Статичний» означає незалежний від контексту, не заморожений назавжди. Вектори слів можна налаштовувати або перенавчати. Звичайний пошук word2vec не має запису для невидимого слова, тому йому потрібна політика невідомих токенів або інше представлення для збільшення словникового запасу.

fastText частково вирішує проблему невидимих слів шляхом складання векторів із n-грам символів. Його представлення слів залишається незалежним від контексту, тому обробка невидимого написання та визначення сенсу слова в реченні є окремими проблемами.

Що буде далі

ELMo використовував стани двонаправленої мовної моделі для представлення токенів у контексті; BERT використовує трансформаторний кодер, навчений передбаченню замаскованих маркерів. Щоб продовжити ціль навчання тут, прочитайте негативна вибірка та контрастне навчання, де слідує відмінність між втратою сигмоїди word2vec та втратою softmax у CLIP.

Перегляд зображення

Змінюйте масштаб жестом щипка або клавішами + / −. Переміщуйте зображення перетягуванням або стрілками. Esc — закрити.