Word2Vec

ดูบน TensorFlow.org ทำงานใน Google Colab ดูแหล่งที่มาบน GitHubดาวน์โหลดโน๊ตบุ๊ค

Word2Vec ไม่ใช่อัลกอริธึมเอกพจน์ แต่เป็นตระกูลของสถาปัตยกรรมแบบจำลองและการเพิ่มประสิทธิภาพที่สามารถใช้ในการเรียนรู้การฝังคำจากชุดข้อมูลขนาดใหญ่ การฝังที่เรียนรู้ผ่าน Word2Vec ได้พิสูจน์แล้วว่าประสบความสำเร็จในงานประมวลผลภาษาธรรมชาติดาวน์สตรีมที่หลากหลาย

เอกสารเหล่านี้เสนอวิธีการเรียนรู้การแทนคำสองวิธี:

  • โมเดล Bag-of-Words ต่อเนื่อง ซึ่งทำนายคำกลางตามคำบริบทโดยรอบ บริบทประกอบด้วยคำสองสามคำก่อนและหลังคำปัจจุบัน (กลาง) สถาปัตยกรรมนี้เรียกว่าแบบจำลองถุงคำ เนื่องจากลำดับของคำในบริบทไม่สำคัญ
  • แบบจำลองข้ามแกรมแบบต่อเนื่อง ซึ่งทำนายคำภายในช่วงที่กำหนดก่อนและหลังคำปัจจุบันในประโยคเดียวกัน ตัวอย่างการทำงานของสิ่งนี้ได้รับด้านล่าง

คุณจะใช้วิธีการข้ามกรัมในบทช่วยสอนนี้ ขั้นแรก คุณจะสำรวจข้ามกรัมและแนวคิดอื่น ๆ โดยใช้ประโยคเดียวสำหรับภาพประกอบ ต่อไป คุณจะฝึกโมเดล Word2Vec ของคุณเองบนชุดข้อมูลขนาดเล็ก บทช่วยสอนนี้ยังมีโค้ดสำหรับส่งออกการฝังที่ได้รับการฝึกอบรมและแสดงภาพใน TensorFlow Embedding Projector

ข้ามกรัมและสุ่มตัวอย่างเชิงลบ Negative

ในขณะที่แบบจำลองคำศัพท์แต่ละคำทำนายคำที่กำหนดบริบทข้างเคียง แบบจำลองข้ามกรัมทำนายบริบท (หรือเพื่อนบ้าน) ของคำ โดยให้คำนั้นเอง โมเดลนี้ได้รับการฝึกอบรมเกี่ยวกับ skip-grams ซึ่งเป็น n-gram ที่อนุญาตให้ข้ามโทเค็น (ดูตัวอย่างแผนภาพด้านล่าง) บริบทของคำสามารถแสดงผ่านชุดของคู่ข้ามแกรมของ (target_word, context_word) โดยที่ context_word ปรากฏในบริบทใกล้เคียงของ target_word

พิจารณาประโยคต่อไปนี้ 8 คำ

ถนนกว้างส่องแสงระยิบระยับท่ามกลางแสงแดดอันร้อนแรง

คำบริบทสำหรับแต่ละคำ 8 คำของประโยคนี้ถูกกำหนดโดยขนาดหน้าต่าง ขนาดหน้าต่างกำหนดช่วงของคำที่ด้านใดด้านหนึ่งของ target_word ที่สามารถถือเป็น context word ดูตารางการข้ามกรัมนี้สำหรับคำเป้าหมายตามขนาดหน้าต่างต่างๆ

word2vec_skipgrams

วัตถุประสงค์การฝึกอบรมของโมเดลข้ามแกรมคือเพื่อเพิ่มความน่าจะเป็นสูงสุดในการทำนายคำบริบทที่กำหนดให้กับคำเป้าหมาย สำหรับลำดับของคำ w 1 , w 2 , ... w T วัตถุประสงค์สามารถเขียนเป็นค่าความน่าจะเป็นของบันทึกเฉลี่ย

word2vec_skipgram_objective

โดยที่ c คือขนาดของบริบทการฝึกอบรม สูตรข้ามกรัมพื้นฐานกำหนดความน่าจะเป็นนี้โดยใช้ฟังก์ชัน softmax

word2vec_full_softmax

โดยที่ v และ v ' เป็นเป้าหมายและการแสดงเวกเตอร์บริบทของคำ และ W คือขนาดของคำศัพท์

การคำนวณตัวส่วนของสูตรนี้เกี่ยวข้องกับการทำ softmax แบบเต็มเหนือคำศัพท์ทั้งหมด ซึ่งมักจะเป็นคำศัพท์ขนาดใหญ่ (10 5 -10 7 )

ฟังก์ชันการสูญเสีย การประมาณค่าเสียงรบกวน เป็นการประมาณที่มีประสิทธิภาพสำหรับซอฟต์แม็กซ์แบบเต็ม โดยมีวัตถุประสงค์เพื่อเรียนรู้การฝังคำแทนการสร้างแบบจำลองการกระจายคำ การสูญเสีย NCE สามารถ ทำให้ง่าย ขึ้นได้โดยใช้การสุ่มตัวอย่างเชิงลบ

วัตถุประสงค์ในการสุ่มตัวอย่างเชิงลบอย่างง่ายสำหรับคำเป้าหมายคือการแยกแยะคำบริบทจากตัวอย่างเชิงลบ num_ns ที่ ดึงมาจากการกระจายสัญญาณรบกวน P n (w) ของคำ ที่แม่นยำยิ่งขึ้น การประมาณค่าซอฟต์แม็กซ์แบบเต็มอย่างมีประสิทธิภาพเหนือคำศัพท์สำหรับคู่ข้ามกรัม ทำให้เกิดการสูญเสียสำหรับคำเป้าหมายเป็นปัญหาการจัดหมวดหมู่ระหว่างคำบริบทและตัวอย่างเชิงลบ num_ns

ตัวอย่างเชิงลบถูกกำหนดให้เป็นคู่ (target_word, context_word) โดยที่ Context_word จะไม่ปรากฏในบริเวณใกล้เคียง window_size ของ target_word สำหรับประโยคตัวอย่าง ตัวอย่างเหล่านี้เป็นเพียงตัวอย่างเชิงลบที่อาจเกิดขึ้น (เมื่อ window_size คือ 2)

(hot, shimmered)
(wide, hot)
(wide, sun)

ในส่วนถัดไป คุณจะสร้างตัวอย่างข้ามกรัมและตัวอย่างเชิงลบสำหรับประโยคเดียว นอกจากนี้คุณยังจะได้เรียนรู้เกี่ยวกับเทคนิคการสุ่มตัวอย่างย่อยและฝึกแบบจำลองการจัดหมวดหมู่สำหรับตัวอย่างการฝึกอบรมเชิงบวกและเชิงลบในบทช่วยสอนในภายหลัง

ติดตั้ง

import io
import re
import string
import tensorflow as tf
import tqdm

from tensorflow.keras import Model
from tensorflow.keras.layers import Dot, Embedding, Flatten
from tensorflow.keras.layers.experimental.preprocessing import TextVectorization
# Load the TensorBoard notebook extension
%load_ext tensorboard
SEED = 42
AUTOTUNE = tf.data.AUTOTUNE

Vectorize ประโยคตัวอย่าง

พิจารณาประโยคต่อไปนี้:
The wide road shimmered in the hot sun.

Tokenize ประโยค:

sentence = "The wide road shimmered in the hot sun"
tokens = list(sentence.lower().split())
print(len(tokens))
8

สร้างคำศัพท์เพื่อบันทึกการจับคู่จากโทเค็นไปยังดัชนีจำนวนเต็ม

vocab, index = {}, 1  # start indexing from 1
vocab['<pad>'] = 0  # add a padding token
for token in tokens:
  if token not in vocab:
    vocab[token] = index
    index += 1
vocab_size = len(vocab)
print(vocab)
{'<pad>': 0, 'the': 1, 'wide': 2, 'road': 3, 'shimmered': 4, 'in': 5, 'hot': 6, 'sun': 7}

สร้างคำศัพท์ผกผันเพื่อบันทึกการจับคู่จากดัชนีจำนวนเต็มไปยังโทเค็น

inverse_vocab = {index: token for token, index in vocab.items()}
print(inverse_vocab)
{0: '<pad>', 1: 'the', 2: 'wide', 3: 'road', 4: 'shimmered', 5: 'in', 6: 'hot', 7: 'sun'}

Vectorize ประโยคของคุณ

example_sequence = [vocab[word] for word in tokens]
print(example_sequence)
[1, 2, 3, 4, 5, 1, 6, 7]

สร้างข้ามกรัมจากหนึ่งประโยค

โมดูล tf.keras.preprocessing.sequence มีฟังก์ชันที่เป็นประโยชน์ซึ่งช่วยลดความยุ่งยากในการเตรียมข้อมูลสำหรับ Word2Vec คุณสามารถใช้ tf.keras.preprocessing.sequence.skipgrams เพื่อสร้างคู่ข้ามกรัมจาก example_sequence ด้วย window_size กำหนดจากโทเค็นในช่วง [0, vocab_size)

window_size = 2
positive_skip_grams, _ = tf.keras.preprocessing.sequence.skipgrams(
      example_sequence,
      vocabulary_size=vocab_size,
      window_size=window_size,
      negative_samples=0)
print(len(positive_skip_grams))
26

ลองดูที่ข้ามกรัมบวกไม่กี่

for target, context in positive_skip_grams[:5]:
  print(f"({target}, {context}): ({inverse_vocab[target]}, {inverse_vocab[context]})")
(1, 4): (the, shimmered)
(4, 3): (shimmered, road)
(4, 5): (shimmered, in)
(3, 2): (road, wide)
(1, 7): (the, sun)

การสุ่มตัวอย่างเชิงลบสำหรับหนึ่ง skip-gram

ฟังก์ชัน skipgrams คืนค่าคู่ของ skip-gram ที่เป็นค่าบวกทั้งหมดโดยเลื่อนผ่านช่วงหน้าต่างที่กำหนด ในการสร้างคู่ข้ามกรัมเพิ่มเติมที่จะใช้เป็นตัวอย่างเชิงลบสำหรับการฝึกอบรม คุณต้องสุ่มตัวอย่างคำจากคำศัพท์ ใช้ฟังก์ชัน tf.random.log_uniform_candidate_sampler เพื่อสุ่มตัวอย่างจำนวนตัวอย่างเชิงลบจำนวน num_ns สำหรับคำเป้าหมายที่กำหนดในหน้าต่าง คุณสามารถเรียกใช้ฟังก์ชันบนคำเป้าหมายของข้ามกรัมหนึ่งคำ และส่งคำบริบทเป็นคลาสจริงเพื่อแยกจากการสุ่มตัวอย่าง

# Get target and context words for one positive skip-gram.
target_word, context_word = positive_skip_grams[0]

# Set the number of negative samples per positive context.
num_ns = 4

context_class = tf.reshape(tf.constant(context_word, dtype="int64"), (1, 1))
negative_sampling_candidates, _, _ = tf.random.log_uniform_candidate_sampler(
    true_classes=context_class,  # class that should be sampled as 'positive'
    num_true=1,  # each positive skip-gram has 1 positive context class
    num_sampled=num_ns,  # number of negative context words to sample
    unique=True,  # all the negative samples should be unique
    range_max=vocab_size,  # pick index of the samples from [0, vocab_size]
    seed=SEED,  # seed for reproducibility
    name="negative_sampling"  # name of this operation
)
print(negative_sampling_candidates)
print([inverse_vocab[index.numpy()] for index in negative_sampling_candidates])
tf.Tensor([2 1 4 3], shape=(4,), dtype=int64)
['wide', 'the', 'shimmered', 'road']

สร้างตัวอย่างการฝึกอบรมหนึ่งตัวอย่าง

สำหรับค่าบวกที่กำหนด (target_word, context_word) ข้ามกรัม ตอนนี้คุณยังมีคำบริบทตัวอย่างเชิงลบ num_ns ที่ไม่ปรากฏในพื้นที่ใกล้เคียงขนาดหน้าต่างของ target_word ชุด 1 บวก context_word และ num_ns เชิงลบคำบริบทเป็นหนึ่งในเมตริกซ์ สิ่งนี้จะสร้างชุดของข้ามกรัมที่เป็นบวก (ติดป้ายกำกับว่า 1 ) และตัวอย่างเชิงลบ (ติดป้ายกำกับเป็น 0 ) สำหรับแต่ละคำเป้าหมาย

# Add a dimension so you can use concatenation (on the next step).
negative_sampling_candidates = tf.expand_dims(negative_sampling_candidates, 1)

# Concat positive context word with negative sampled words.
context = tf.concat([context_class, negative_sampling_candidates], 0)

# Label first context word as 1 (positive) followed by num_ns 0s (negative).
label = tf.constant([1] + [0]*num_ns, dtype="int64")

# Reshape target to shape (1,) and context and label to (num_ns+1,).
target = tf.squeeze(target_word)
context = tf.squeeze(context)
label = tf.squeeze(label)

ดูบริบทและป้ายกำกับที่เกี่ยวข้องสำหรับคำเป้าหมายจากตัวอย่างข้ามแกรมด้านบน

print(f"target_index    : {target}")
print(f"target_word     : {inverse_vocab[target_word]}")
print(f"context_indices : {context}")
print(f"context_words   : {[inverse_vocab[c.numpy()] for c in context]}")
print(f"label           : {label}")
target_index    : 1
target_word     : the
context_indices : [4 2 1 4 3]
context_words   : ['shimmered', 'wide', 'the', 'shimmered', 'road']
label           : [1 0 0 0 0]

ทูเพิลของเมตริกซ์ (target, context, label) ถือเป็นตัวอย่างการฝึกอบรมหนึ่งตัวอย่างสำหรับการฝึกโมเดล Word2Vec การสุ่มตัวอย่างเชิงลบข้ามกรัม สังเกตว่าเป้าหมายมีรูปร่าง (1,) ในขณะที่บริบทและป้ายกำกับมีรูปร่าง (1+num_ns,)

print("target  :", target)
print("context :", context)
print("label   :", label)
target  : tf.Tensor(1, shape=(), dtype=int32)
context : tf.Tensor([4 2 1 4 3], shape=(5,), dtype=int64)
label   : tf.Tensor([1 0 0 0 0], shape=(5,), dtype=int64)

สรุป

ภาพนี้สรุปขั้นตอนการสร้างตัวอย่างการฝึกจากประโยค

word2vec_negative_sampling

รวบรวมขั้นตอนทั้งหมดไว้ในฟังก์ชันเดียว

ตารางสุ่มตัวอย่างข้ามกรัม Ski

ชุดข้อมูลขนาดใหญ่หมายถึงคำศัพท์ที่มากขึ้นด้วยจำนวนคำที่ใช้บ่อยมากขึ้น เช่น คำหยุด ตัวอย่างการฝึกอบรมที่ได้จากการสุ่มตัวอย่างคำที่เกิดขึ้นทั่วไป (เช่น the , is , on ) ไม่ได้เพิ่มข้อมูลที่เป็นประโยชน์มากนักสำหรับแบบจำลองที่จะเรียนรู้ มิโคลอฟและคณะ แนะนำการสุ่มตัวอย่างคำที่ใช้บ่อยเป็นแนวทางที่เป็นประโยชน์ในการปรับปรุงคุณภาพการฝัง

ฟังก์ชัน tf.keras.preprocessing.sequence.skipgrams ยอมรับอาร์กิวเมนต์ตารางการสุ่มตัวอย่างเพื่อเข้ารหัสความน่าจะเป็นของการสุ่มตัวอย่างโทเค็นใดๆ คุณสามารถใช้ tf.keras.preprocessing.sequence.make_sampling_table เพื่อสร้างตารางสุ่มตัวอย่างความน่าจะเป็นตามอันดับความถี่คำ และส่งผ่านไปยังฟังก์ชัน skipgrams ดูความน่าจะเป็นของการสุ่มตัวอย่างสำหรับ vocab_size ของ 10

sampling_table = tf.keras.preprocessing.sequence.make_sampling_table(size=10)
print(sampling_table)
[0.00315225 0.00315225 0.00547597 0.00741556 0.00912817 0.01068435
 0.01212381 0.01347162 0.01474487 0.0159558 ]

sampling_table[i] หมายถึงความน่าจะเป็นของการสุ่มตัวอย่างคำทั่วไปที่ i-th ในชุดข้อมูล ฟังก์ชันนี้จะถือว่าการ แจกแจง ความถี่คำ ของ Zipf สำหรับการสุ่มตัวอย่าง

สร้างข้อมูลการฝึกอบรม

รวบรวมขั้นตอนทั้งหมดที่อธิบายไว้ข้างต้นให้เป็นฟังก์ชันที่สามารถเรียกใช้ในรายการประโยคแบบเวกเตอร์ที่ได้รับจากชุดข้อมูลข้อความใดๆ สังเกตว่าตารางการสุ่มตัวอย่างถูกสร้างขึ้นก่อนการสุ่มตัวอย่างคู่คำข้ามแกรม คุณจะใช้ฟังก์ชันนี้ในหัวข้อถัดไป

# Generates skip-gram pairs with negative sampling for a list of sequences
# (int-encoded sentences) based on window size, number of negative samples
# and vocabulary size.
def generate_training_data(sequences, window_size, num_ns, vocab_size, seed):
  # Elements of each training example are appended to these lists.
  targets, contexts, labels = [], [], []

  # Build the sampling table for vocab_size tokens.
  sampling_table = tf.keras.preprocessing.sequence.make_sampling_table(vocab_size)

  # Iterate over all sequences (sentences) in dataset.
  for sequence in tqdm.tqdm(sequences):

    # Generate positive skip-gram pairs for a sequence (sentence).
    positive_skip_grams, _ = tf.keras.preprocessing.sequence.skipgrams(
          sequence,
          vocabulary_size=vocab_size,
          sampling_table=sampling_table,
          window_size=window_size,
          negative_samples=0)

    # Iterate over each positive skip-gram pair to produce training examples
    # with positive context word and negative samples.
    for target_word, context_word in positive_skip_grams:
      context_class = tf.expand_dims(
          tf.constant([context_word], dtype="int64"), 1)
      negative_sampling_candidates, _, _ = tf.random.log_uniform_candidate_sampler(
          true_classes=context_class,
          num_true=1,
          num_sampled=num_ns,
          unique=True,
          range_max=vocab_size,
          seed=SEED,
          name="negative_sampling")

      # Build context and label vectors (for one target word)
      negative_sampling_candidates = tf.expand_dims(
          negative_sampling_candidates, 1)

      context = tf.concat([context_class, negative_sampling_candidates], 0)
      label = tf.constant([1] + [0]*num_ns, dtype="int64")

      # Append each element from the training example to global lists.
      targets.append(target_word)
      contexts.append(context)
      labels.append(label)

  return targets, contexts, labels

เตรียมข้อมูลการฝึกอบรมสำหรับ Word2Vec

ด้วยความเข้าใจเกี่ยวกับวิธีการทำงานกับหนึ่งประโยคสำหรับการสุ่มตัวอย่างเชิงลบแบบข้ามกรัมตามแบบจำลอง Word2Vec คุณสามารถดำเนินการสร้างตัวอย่างการฝึกอบรมจากรายการประโยคที่ใหญ่ขึ้นได้!

ดาวน์โหลดคลังข้อความ

คุณจะใช้ไฟล์ข้อความของงานเขียนของเช็คสเปียร์สำหรับบทช่วยสอนนี้ เปลี่ยนบรรทัดต่อไปนี้เพื่อเรียกใช้รหัสนี้กับข้อมูลของคุณเอง

path_to_file = tf.keras.utils.get_file('shakespeare.txt', 'https://storage.googleapis.com/download.tensorflow.org/data/shakespeare.txt')
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/shakespeare.txt
1122304/1115394 [==============================] - 0s 0us/step

อ่านข้อความจากไฟล์และดูสองสามบรรทัดแรก

with open(path_to_file) as f: 
  lines = f.read().splitlines()
for line in lines[:20]:
  print(line)
First Citizen:
Before we proceed any further, hear me speak.

All:
Speak, speak.

First Citizen:
You are all resolved rather to die than to famish?

All:
Resolved. resolved.

First Citizen:
First, you know Caius Marcius is chief enemy to the people.

All:
We know't, we know't.

First Citizen:
Let us kill him, and we'll have corn at our own price.

ใช้บรรทัดที่ไม่ว่างเพื่อสร้างอ็อบเจ็กต์ tf.data.TextLineDataset สำหรับขั้นตอนต่อไป

text_ds = tf.data.TextLineDataset(path_to_file).filter(lambda x: tf.cast(tf.strings.length(x), bool))

Vectorize ประโยคจากคลัง

คุณสามารถใช้เลเยอร์ TextVectorization เพื่อสร้างประโยคเวกเตอร์จากคลังข้อมูลได้ เรียนรู้เพิ่มเติมเกี่ยวกับการใช้เลเยอร์นี้ในบทช่วยสอน การจัดประเภทข้อความ นี้ สังเกตจากสองสามประโยคแรกข้างต้นว่าข้อความต้องอยู่ในตัวพิมพ์เดียวและต้องลบเครื่องหมายวรรคตอน เมื่อต้องการทำเช่นนี้ ให้กำหนด custom_standardization function ที่สามารถใช้ในเลเยอร์ TextVectorization

# Now, create a custom standardization function to lowercase the text and
# remove punctuation.
def custom_standardization(input_data):
  lowercase = tf.strings.lower(input_data)
  return tf.strings.regex_replace(lowercase,
                                  '[%s]' % re.escape(string.punctuation), '')


# Define the vocabulary size and number of words in a sequence.
vocab_size = 4096
sequence_length = 10

# Use the text vectorization layer to normalize, split, and map strings to
# integers. Set output_sequence_length length to pad all samples to same length.
vectorize_layer = TextVectorization(
    standardize=custom_standardization,
    max_tokens=vocab_size,
    output_mode='int',
    output_sequence_length=sequence_length)

โทร adapt ชุดข้อมูลข้อความเพื่อสร้างคำศัพท์

vectorize_layer.adapt(text_ds.batch(1024))

เมื่อสถานะของเลเยอร์ได้รับการปรับให้เป็นตัวแทนของคลังข้อความ คำศัพท์สามารถเข้าถึงได้ด้วย get_vocabulary() ฟังก์ชันนี้จะส่งคืนรายการโทเค็นคำศัพท์ทั้งหมดที่จัดเรียง (จากมากไปน้อย) ตามความถี่

# Save the created vocabulary for reference.
inverse_vocab = vectorize_layer.get_vocabulary()
print(inverse_vocab[:20])
['', '[UNK]', 'the', 'and', 'to', 'i', 'of', 'you', 'my', 'a', 'that', 'in', 'is', 'not', 'for', 'with', 'me', 'it', 'be', 'your']

vectorize_layer สามารถใช้เพื่อสร้างเวกเตอร์สำหรับแต่ละองค์ประกอบใน text_ds

# Vectorize the data in text_ds.
text_vector_ds = text_ds.batch(1024).prefetch(AUTOTUNE).map(vectorize_layer).unbatch()

รับลำดับจากชุดข้อมูล

ตอนนี้คุณมีtf.data.Dataset ของประโยคที่เข้ารหัสจำนวนเต็มแล้ว ในการเตรียมชุดข้อมูลสำหรับการฝึกโมเดล Word2Vec ให้ปรับชุดข้อมูลให้เป็นรายการลำดับเวกเตอร์ประโยค ขั้นตอนนี้จำเป็นเมื่อคุณทำซ้ำในแต่ละประโยคในชุดข้อมูลเพื่อสร้างตัวอย่างเชิงบวกและเชิงลบ

sequences = list(text_vector_ds.as_numpy_iterator())
print(len(sequences))
32777

ดูตัวอย่างบางส่วนจาก sequences

for seq in sequences[:5]:
  print(f"{seq} => {[inverse_vocab[i] for i in seq]}")
[ 89 270   0   0   0   0   0   0   0   0] => ['first', 'citizen', '', '', '', '', '', '', '', '']
[138  36 982 144 673 125  16 106   0   0] => ['before', 'we', 'proceed', 'any', 'further', 'hear', 'me', 'speak', '', '']
[34  0  0  0  0  0  0  0  0  0] => ['all', '', '', '', '', '', '', '', '', '']
[106 106   0   0   0   0   0   0   0   0] => ['speak', 'speak', '', '', '', '', '', '', '', '']
[ 89 270   0   0   0   0   0   0   0   0] => ['first', 'citizen', '', '', '', '', '', '', '', '']

สร้างตัวอย่างการฝึกอบรมจากซีเควนซ์

ตอนนี้ sequences เป็นรายการของประโยคที่เข้ารหัส int เพียงเรียกใช้ฟังก์ชัน generate_training_data() กำหนดไว้ก่อนหน้านี้เพื่อสร้างตัวอย่างการฝึกอบรมสำหรับโมเดล Word2Vec เพื่อสรุป ฟังก์ชันจะวนซ้ำแต่ละคำจากแต่ละลำดับเพื่อรวบรวมคำในบริบทเชิงบวกและเชิงลบ ความยาวของเป้าหมาย บริบท และป้ายกำกับควรเท่ากัน ซึ่งแสดงถึงจำนวนตัวอย่างการฝึกอบรมทั้งหมด

targets, contexts, labels = generate_training_data(
    sequences=sequences,
    window_size=2,
    num_ns=4,
    vocab_size=vocab_size,
    seed=SEED)
print(len(targets), len(contexts), len(labels))
100%|██████████| 32777/32777 [00:25<00:00, 1267.26it/s]
64720 64720 64720

กำหนดค่าชุดข้อมูลสำหรับประสิทธิภาพ

ในการดำเนินการแบตช์อย่างมีประสิทธิภาพสำหรับตัวอย่างการฝึกจำนวนมาก ให้ใช้tf.data.Dataset API หลังจากขั้นตอนนี้ คุณจะมีอ็อบเจ็กต์tf.data.Dataset ของ (target_word, context_word), (label) เพื่อฝึกโมเดล Word2Vec ของคุณ!

BATCH_SIZE = 1024
BUFFER_SIZE = 10000
dataset = tf.data.Dataset.from_tensor_slices(((targets, contexts), labels))
dataset = dataset.shuffle(BUFFER_SIZE).batch(BATCH_SIZE, drop_remainder=True)
print(dataset)
<BatchDataset shapes: (((1024,), (1024, 5, 1)), (1024, 5)), types: ((tf.int32, tf.int64), tf.int64)>

เพิ่ม cache() และ prefetch() เพื่อปรับปรุงประสิทธิภาพ

dataset = dataset.cache().prefetch(buffer_size=AUTOTUNE)
print(dataset)
<PrefetchDataset shapes: (((1024,), (1024, 5, 1)), (1024, 5)), types: ((tf.int32, tf.int64), tf.int64)>

โมเดลและการฝึกอบรม

โมเดล Word2Vec สามารถนำไปใช้เป็นตัวแยกประเภทเพื่อแยกความแตกต่างระหว่างคำบริบทจริงจากข้ามกรัมและคำบริบทเท็จที่ได้รับจากการสุ่มตัวอย่างเชิงลบ คุณสามารถดำเนินการดอทผลิตภัณฑ์ระหว่างการฝังของเป้าหมายและคำบริบทเพื่อรับการคาดการณ์สำหรับป้ายกำกับและคำนวณการสูญเสียกับป้ายกำกับจริงในชุดข้อมูล

รุ่นย่อยของ Word2Vec Model

ใช้ Keras Subclassing API เพื่อกำหนดโมเดล Word2Vec ของคุณด้วยเลเยอร์ต่อไปนี้:

  • target_embedding : เลเยอร์ tf.keras.layers.Embedding ซึ่งจะค้นหาการฝังคำเมื่อปรากฏเป็นคำเป้าหมาย จำนวนพารามิเตอร์ในเลเยอร์นี้คือ (vocab_size * embedding_dim)
  • context_embedding : อีก tf.keras.layers.Embedding ชั้นซึ่งมีลักษณะการฝังขึ้นของคำเมื่อมันปรากฏเป็นคำบริบท จำนวนพารามิเตอร์ในเลเยอร์นี้เท่ากับพารามิเตอร์ใน target_embedding เช่น (vocab_size * embedding_dim)
  • dots : เลเยอร์ tf.keras.layers.Dot ที่คำนวณผลิตภัณฑ์ dot ของการฝังเป้าหมายและบริบทจากคู่การฝึก
  • flatten : A tf.keras.layers.Flatten layer เพื่อทำให้ผลลัพธ์ของชั้น dots เรียบลงใน logits

ด้วยโมเดลคลาสย่อย คุณสามารถกำหนดฟังก์ชัน call() ที่ยอมรับคู่ (target, context) ซึ่งสามารถส่งต่อไปยังเลเยอร์การฝังที่สอดคล้องกันได้ ก่อร่างใหม่ context_embedding เพื่อดำเนินการคูณจุดที่มี target_embedding และกลับผลบี้

class Word2Vec(Model):
  def __init__(self, vocab_size, embedding_dim):
    super(Word2Vec, self).__init__()
    self.target_embedding = Embedding(vocab_size,
                                      embedding_dim,
                                      input_length=1,
                                      name="w2v_embedding")
    self.context_embedding = Embedding(vocab_size,
                                       embedding_dim,
                                       input_length=num_ns+1)
    self.dots = Dot(axes=(3, 2))
    self.flatten = Flatten()

  def call(self, pair):
    target, context = pair
    word_emb = self.target_embedding(target)
    context_emb = self.context_embedding(context)
    dots = self.dots([context_emb, word_emb])
    return self.flatten(dots)

กำหนดฟังก์ชันการสูญเสียและคอมไพล์โมเดล

เพื่อความเรียบง่าย คุณสามารถใช้ tf.keras.losses.CategoricalCrossEntropy เป็นทางเลือกแทนการสูญเสียการสุ่มตัวอย่างเชิงลบ หากคุณต้องการเขียนฟังก์ชันการสูญเสียที่กำหนดเอง คุณสามารถทำได้ดังนี้:

def custom_loss(x_logit, y_true):
      return tf.nn.sigmoid_cross_entropy_with_logits(logits=x_logit, labels=y_true)

ได้เวลาสร้างแบบจำลองของคุณแล้ว! สร้างอินสแตนซ์คลาส Word2Vec ของคุณด้วยขนาดการฝัง 128 (คุณสามารถทดลองด้วยค่าต่างๆ ได้) รวบรวมโมเดลด้วย tf.keras.optimizers.Adam เพิ่มประสิทธิภาพ tf.keras.optimizers.Adam

embedding_dim = 128
word2vec = Word2Vec(vocab_size, embedding_dim)
word2vec.compile(optimizer='adam',
                 loss=tf.keras.losses.CategoricalCrossentropy(from_logits=True),
                 metrics=['accuracy'])

กำหนดการโทรกลับเพื่อบันทึกสถิติการฝึกสำหรับเทนเซอร์บอร์ดด้วย

tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir="logs")

ฝึกโมเดลด้วย dataset เตรียมไว้ด้านบนสำหรับบางช่วง

word2vec.fit(dataset, epochs=20, callbacks=[tensorboard_callback])
Epoch 1/20
63/63 [==============================] - 2s 11ms/step - loss: 1.6082 - accuracy: 0.2308
Epoch 2/20
63/63 [==============================] - 0s 5ms/step - loss: 1.5889 - accuracy: 0.5539
Epoch 3/20
63/63 [==============================] - 0s 5ms/step - loss: 1.5412 - accuracy: 0.6001
Epoch 4/20
63/63 [==============================] - 0s 5ms/step - loss: 1.4585 - accuracy: 0.5751
Epoch 5/20
63/63 [==============================] - 0s 5ms/step - loss: 1.3600 - accuracy: 0.5834
Epoch 6/20
63/63 [==============================] - 0s 5ms/step - loss: 1.2628 - accuracy: 0.6110
Epoch 7/20
63/63 [==============================] - 0s 5ms/step - loss: 1.1719 - accuracy: 0.6456
Epoch 8/20
63/63 [==============================] - 0s 5ms/step - loss: 1.0875 - accuracy: 0.6791
Epoch 9/20
63/63 [==============================] - 0s 5ms/step - loss: 1.0092 - accuracy: 0.7101
Epoch 10/20
63/63 [==============================] - 0s 5ms/step - loss: 0.9366 - accuracy: 0.7379
Epoch 11/20
63/63 [==============================] - 0s 5ms/step - loss: 0.8695 - accuracy: 0.7622
Epoch 12/20
63/63 [==============================] - 0s 5ms/step - loss: 0.8076 - accuracy: 0.7848
Epoch 13/20
63/63 [==============================] - 0s 5ms/step - loss: 0.7507 - accuracy: 0.8050
Epoch 14/20
63/63 [==============================] - 0s 5ms/step - loss: 0.6985 - accuracy: 0.8217
Epoch 15/20
63/63 [==============================] - 0s 5ms/step - loss: 0.6507 - accuracy: 0.8368
Epoch 16/20
63/63 [==============================] - 0s 5ms/step - loss: 0.6071 - accuracy: 0.8506
Epoch 17/20
63/63 [==============================] - 0s 5ms/step - loss: 0.5673 - accuracy: 0.8633
Epoch 18/20
63/63 [==============================] - 0s 5ms/step - loss: 0.5311 - accuracy: 0.8741
Epoch 19/20
63/63 [==============================] - 0s 5ms/step - loss: 0.4981 - accuracy: 0.8842
Epoch 20/20
63/63 [==============================] - 0s 5ms/step - loss: 0.4681 - accuracy: 0.8936
<tensorflow.python.keras.callbacks.History at 0x7f120c5b5210>

ขณะนี้ Tensorboard แสดงความแม่นยำและการสูญเสียของรุ่น Word2Vec

%tensorboard --logdir logs

ฝังการค้นหาและการวิเคราะห์

รับน้ำหนักจากโมเดลโดยใช้ get_layer() และ get_weights() get_vocabulary() จัดเตรียมคำศัพท์เพื่อสร้างไฟล์ข้อมูลเมตาด้วยหนึ่งโทเค็นต่อบรรทัด

weights = word2vec.get_layer('w2v_embedding').get_weights()[0]
vocab = vectorize_layer.get_vocabulary()

สร้างและบันทึกเวกเตอร์และไฟล์ข้อมูลเมตา

out_v = io.open('vectors.tsv', 'w', encoding='utf-8')
out_m = io.open('metadata.tsv', 'w', encoding='utf-8')

for index, word in enumerate(vocab):
  if index == 0:
    continue  # skip 0, it's padding.
  vec = weights[index]
  out_v.write('\t'.join([str(x) for x in vec]) + "\n")
  out_m.write(word + "\n")
out_v.close()
out_m.close()

ดาวน์โหลด vectors.tsv และ metadata.tsv เพื่อวิเคราะห์การฝังที่ได้รับใน Embedding Projector

try:
  from google.colab import files
  files.download('vectors.tsv')
  files.download('metadata.tsv')
except Exception:
  pass

ขั้นตอนถัดไป

บทช่วยสอนนี้แสดงให้คุณเห็นถึงวิธีการใช้โมเดล Word2Vec แบบข้ามแกรมที่มีการสุ่มตัวอย่างเชิงลบตั้งแต่เริ่มต้น และแสดงภาพการฝังคำที่ได้รับ